├── .bowerrc
├── .buildpath
├── .gitignore
├── .project
├── README.md
├── bower.json
├── gulpfile.js
├── package.json
├── postinstall
├── gulp-replace.js
├── overwrite-backbone.js
├── overwrite-marionette.js
├── postinstall.js
└── postinstall.md
└── src
├── data
├── README.md
└── frameworks.json
└── js
├── client
├── .gitignore
├── bootstrap.js
└── bundle.js
├── common
└── views
│ ├── backbone-view
│ ├── backbone-view.js
│ └── backbone-view.tpl
│ ├── framework
│ ├── framework-layout-view.js
│ └── framework-layout-view.tpl
│ ├── item-view
│ ├── item-view.js
│ └── item-view.tpl
│ └── layout-view
│ ├── layout-view.js
│ └── layout-view.tpl
└── server
├── app.tpl
├── controller
├── framework-controller.js
└── index.js
├── dal
└── data-mapper
│ └── framework-data-mapper.js
├── db.js
├── document.js
├── server.js
└── service
└── framework-service.js
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "src/js/client/bower_components"
3 | }
--------------------------------------------------------------------------------
/.buildpath:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | /node_modules/
2 | /tmp/
3 |
--------------------------------------------------------------------------------
/.project:
--------------------------------------------------------------------------------
1 |
2 |
3 | backbone.isomorphic
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # backbone.isomorphic
2 |
3 | > Isomorphic Backbone and Marionette
4 |
5 | ## Quick start
6 |
7 | Install and setup the following tools :
8 | * [node.js](https://nodejs.org "node.js") or [io.js](https://iojs.org "io.js")
9 | * [Gulp](http://gulpjs.com "gulp.js")
10 |
11 | Run the following commands to initialize the project.
12 | ```
13 | npm install
14 | gulp init
15 | ```
16 |
17 | Then run `gulp serve` and open [http://localhost:3000](http://localhost:3000 "demo") in your browser, you'll see a
18 | Backbone and Marionette isomorphic application demo ;-)
19 |
20 | ## Using isomorphic Backbone / Marionette
21 |
22 | ### The `Backbone.isomorphic` parameter
23 |
24 | **TODO**
25 |
26 | ### Make node.js require work with templates
27 |
28 | **TODO**
29 |
30 | ## How does it work ?
31 |
32 | ### Server side DOM rendering
33 |
34 | **TODO**
35 |
36 | ### Library overwrites
37 |
38 | backbone.isomorphic provides npm postinstall scripts which overwrites the `backbone.js` and `marionette.js` library
39 | files. The advantage of this method is that you can continue to use standard Backbone and Marionette in your projects,
40 | then the required updates to make then work in an isomorphic environment are automatically applied for you.
41 |
42 | When you run the `npm install` command some postinstall scripts are automatically executed and the following files are
43 | dynamically modified :
44 | * `node_modules/backbone/backbone.js`
45 | * `node_modules/backbone.marionette/node_modules/backbone/backbone.js`
46 |
47 | The following sections describes the modifications which are applied in those files.
48 |
49 | #### backbone.js
50 |
51 | **TODO**
52 |
53 | #### marionette.js
54 |
55 | **TODO**
56 |
57 |
58 | ## Gulp tasks
59 |
60 | ### Pull bower dependencies
61 |
62 | Pull bower dependencies using the following command.
63 |
64 | ```
65 | bower update
66 | ```
67 |
68 | ### Creates bundle.js file
69 |
70 | The project uses Browserify to create a bundled application javascript file in `src/client/bundle.js`.
71 |
72 | ```
73 | gulp browserify
74 | ```
75 |
76 | ### Start the server
77 |
78 | Use the following command to start a test server and go to http://localhost:3000 to see the results.
79 |
80 | ```
81 | gulp serve
82 | ```
83 |
84 | Great Backbone and Marionette views rendered server side ;-) !
--------------------------------------------------------------------------------
/bower.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backbone.isomorphic",
3 | "description" : "Backbone and Marionette isomorphic Proof Of Concepts.",
4 | "version" : "0.1.0",
5 | "license": "MIT",
6 | "homepage": "https://github.com/gomoob/backbone.isomorphic",
7 | "authors": [
8 | {
9 | "name" : "Baptiste Gaillard",
10 | "email" : "baptiste.gaillard@gomoob.com"
11 | }
12 | ],
13 | "keywords" : [
14 | "backbone",
15 | "marionette",
16 | "isomorphic",
17 | "server",
18 | "server-side",
19 | "jsdom",
20 | "domino",
21 | "express"
22 | ],
23 | "main" : [
24 | "dist/backbone.isomorphic.min.js"
25 | ],
26 | "ignore": [
27 | "reports",
28 | "test",
29 | ".gitignore",
30 | ".jshintrc",
31 | ".npmignore",
32 | ".project",
33 | ".travis.yml",
34 | "Gruntfile.js",
35 | "package.json"
36 | ],
37 | "dependencies": {
38 | "backbone": "1.0.0 - 1.1.2",
39 | "backbone.marionette": "^2.4.1",
40 | "bootstrap": "^3.3.4",
41 | "bootstrap-material-design": "^0.3.0",
42 | "jquery": "^2.1.4",
43 | "underscore": "1.4.4 - 1.6.0"
44 | },
45 | "devDependencies": {}
46 | }
47 |
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | var bower = require('gulp-bower');
2 | browserify = require('browserify'),
3 | glob = require('glob'),
4 | gulp = require('gulp'),
5 | gutil = require('gulp-util');
6 | minify = require('html-minifier').minify,
7 | nodemon = require('gulp-nodemon'),
8 | replace = require('gulp-replace'),
9 | source = require('vinyl-source-stream'),
10 | transform = require('vinyl-transform'),
11 | fs = require('fs'),
12 | path = require('path'),
13 | watchify = require('watchify');
14 |
15 | gulp.task('init', ['bower', 'browserify']);
16 |
17 | gulp.task('bower', function() {
18 |
19 | return bower().pipe(gulp.dest('src/js/client/bower_components'))
20 |
21 | });
22 |
23 | /**
24 | * Task used to create the client Javascript file using Browserify. The produced file will be stored in
25 | * 'src/client/bundle.js'.
26 | */
27 | gulp.task(
28 | 'browserify',
29 | ['inline-templates'],
30 | function() {
31 |
32 | return browserify(
33 | {
34 | entries: ['tmp/bootstrap.js'],
35 | debug: true
36 | }
37 | ).bundle()
38 |
39 | // Log errors if they happen
40 | .on('error', gutil.log.bind(gutil, 'Browserify Error'))
41 | .pipe(source('bundle.js'))
42 | .pipe(gulp.dest('src/js/client'));
43 |
44 | }
45 | );
46 |
47 | /**
48 | * Task used to inspect all "require('*.tpl')" calls in the source code and replace those calls by a string with the
49 | * template content.
50 | */
51 | gulp.task(
52 | 'inline-templates',
53 | function() {
54 |
55 | var stream = gulp.src(['src/js/common/views/**/*.js'])
56 | .pipe(replace(/require\(\'.*\.tpl\'\)/g, function(toBeReplaced, file) {
57 |
58 | // Extracts the template relative path
59 | var tplRelativePath = toBeReplaced.substring(9);
60 | tplRelativePath = tplRelativePath.substring(0, tplRelativePath.length - 2);
61 |
62 | // Computes the template absolute path
63 | var tplAbsolutePath = path.join(path.dirname(file.path), tplRelativePath);
64 |
65 | // Gets the template content
66 | var tpl = fs.readFileSync(tplAbsolutePath, 'utf8');
67 |
68 | // Remove line breaks
69 | tpl = tpl.replace(/(\r\n|\n|\r)/gm,"")
70 |
71 | // Minify the template content
72 | tpl = minify(tpl, {
73 | collapseWhitespace : true,
74 | removeComments: true
75 | });
76 |
77 | // Escapte quotes and double quotes
78 | tpl = tpl.replace(/"/g, '\\"').replace(/'/g, "\\'");
79 |
80 | return '\'' + tpl + '\'';
81 |
82 | }))
83 | .pipe(gulp.dest('./tmp/views'));
84 |
85 | return stream;
86 |
87 | }
88 | );
89 |
90 | gulp.task(
91 | 'serve',
92 | function () {
93 | nodemon(
94 | {
95 | cwd: 'src/js/server',
96 | script: 'server.js',
97 | ext: 'js html',
98 | env: {
99 | 'NODE_ENV': 'development'
100 | }
101 | }
102 | );
103 | }
104 | );
105 |
106 | gulp.task(
107 | 'watch',
108 | function() {
109 |
110 | var templateWatcher = gulp.watch('src/js/common/views/**/*', ['inline-templates']);
111 |
112 | var browserified = browserify(
113 | {
114 | entries: ['tmp/bootstrap.js'],
115 | debug: true
116 | }
117 | );
118 | var watchifiedBrowserified = watchify(browserified);
119 | var watchifiedBrowserifiedBundle = createBundle();
120 |
121 | watchifiedBrowserified.on('update', createBundle);
122 | watchifiedBrowserified.on('log', gutil.log);
123 |
124 | function createBundle() {
125 |
126 | return watchifiedBrowserified
127 | .bundle()
128 |
129 | // Log errors if they happen
130 | .on('error', gutil.log.bind(gutil, 'Browserify Error'))
131 | .pipe(source('bundle.js'))
132 | .pipe(gulp.dest('src/js/client'));
133 |
134 | }
135 |
136 | }
137 | );
138 |
139 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "backbone.isomorphic",
3 | "version": "0.1.0",
4 | "description": "Backbone and Marionette isomorphic Proof Of Concepts.",
5 | "main": "server.js",
6 | "repository": "gomoob/backbone.isomorphic",
7 | "keywords": [
8 | "backbone",
9 | "marionette",
10 | "isomorphic",
11 | "server",
12 | "server-side",
13 | "jsdom",
14 | "domino",
15 | "express"
16 | ],
17 | "author": "Baptiste GAILLARD (baptiste.gaillard@gomoob.com)",
18 | "license": "MIT",
19 | "dependencies": {
20 | "backbone": "1.0.0 - 1.1.2",
21 | "backbone.marionette": "^2.4.1",
22 | "domino": "^1.0.18",
23 | "express": "^4.12.4",
24 | "fs-extra": "^0.19.0",
25 | "jquery": "^2.1.4",
26 | "jsdom": "^3.1.2",
27 | "promise": "^7.0.4",
28 | "sqlite3": "^3.0.10",
29 | "underscore": "1.4.4 - 1.6.0"
30 | },
31 | "devDependencies": {
32 | "browserify": "^10.2.4",
33 | "glob": "^5.0.10",
34 | "gulp": "^3.9.0",
35 | "gulp-bower": "0.0.10",
36 | "gulp-nodemon": "^2.0.3",
37 | "gulp-preprocess": "^1.2.0",
38 | "gulp-replace": "^0.5.3",
39 | "gulp-replace-path": "^0.4.0",
40 | "gulp-util": "^3.0.5",
41 | "html-minifier": "^0.7.2",
42 | "vinyl-buffer": "^1.0.0",
43 | "vinyl-source-stream": "^1.1.0",
44 | "vinyl-transform": "^1.0.0",
45 | "watchify": "^3.2.2"
46 | },
47 | "scripts": {
48 | "postinstall": "node postinstall/postinstall.js"
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/postinstall/gulp-replace.js:
--------------------------------------------------------------------------------
1 | 'use strict';
2 |
3 | var through = require('through2');
4 | var rs = require('replacestream');
5 | var istextorbinary = require('istextorbinary');
6 |
7 | module.exports = function(search, replacement, options) {
8 |
9 | var replaceWithFile = function(str, replacementCb, file) {
10 | return str.replace(search, function(val) {
11 | return replacementCb(val, file);
12 | });
13 | };
14 |
15 | var doReplace = function(file, enc, callback) {
16 | if (file.isNull()) {
17 | return callback(null, file);
18 | }
19 |
20 | function doReplace() {
21 | if (file.isStream()) {
22 | file.contents = file.contents.pipe(rs(search, replacement));
23 | return callback(null, file);
24 | }
25 |
26 | if (file.isBuffer()) {
27 | if (search instanceof RegExp) {
28 | file.contents = new Buffer(replaceWithFile(String(file.contents), replacement, file));
29 | }
30 | else {
31 | var chunks = String(file.contents).split(search);
32 |
33 | var result;
34 | if (typeof replacement === 'function') {
35 | // Start with the first chunk already in the result
36 | // Replacements will be added thereafter
37 | // This is done to avoid checking the value of i in the loop
38 | result = [ chunks[0] ];
39 |
40 | // The replacement function should be called once for each match
41 | for (var i = 1; i < chunks.length; i++) {
42 | // Add the replacement value
43 | result.push(replacement(search, file));
44 |
45 | // Add the next chunk
46 | result.push(chunks[i]);
47 | }
48 |
49 | result = result.join('');
50 | }
51 | else {
52 | result = chunks.join(replacement);
53 | }
54 |
55 | file.contents = new Buffer(result);
56 | }
57 | return callback(null, file);
58 | }
59 |
60 | callback(null, file);
61 | }
62 |
63 | if (options && options.skipBinary) {
64 | istextorbinary.isText('', file.contents, function(err, result) {
65 | if (err) {
66 | return callback(err, file);
67 | }
68 |
69 | if (!result) {
70 | callback(null, file);
71 | } else {
72 | doReplace();
73 | }
74 | });
75 |
76 | return;
77 | }
78 |
79 | doReplace();
80 | };
81 |
82 | return through.obj(doReplace);
83 | };
84 |
--------------------------------------------------------------------------------
/postinstall/overwrite-backbone.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 |
3 | module.exports = function(path) {
4 |
5 | var data = fs.readFileSync(path, 'utf8'),
6 | write = false;
7 |
8 | // By default 'backbone.js' suppose we do not need jQuery in a Node.js environment.
9 | // In our case we absolutly need it with Node because Backbone and Marionette view rendering needs jQuery
10 | if(data.indexOf("require('jquery');") === -1) {
11 |
12 | write = true;
13 |
14 | // Require jQuery
15 | data = data.replace(
16 | "var _ = require('underscore');\n",
17 | "var _ = require('underscore');\n var $ = require('jquery');\n"
18 | );
19 |
20 | // Add jQuery as a dependency of the backbone UMD module factory method
21 | data = data.replace(
22 | "factory(root, exports, _);",
23 | "factory(root, exports, _, $);"
24 | );
25 |
26 | }
27 |
28 | if(data.indexOf("Backbone.isomorphic === true;") === -1) {
29 |
30 | write = true;
31 |
32 | data = data.replace(
33 | "Backbone.View = function(options) {\n",
34 | "Backbone.View = function(options) {\n this.isomorphic = Backbone.isomorphic === true;\n"
35 | );
36 |
37 | data = data.replace(
38 | "_ensureElement: function() {\n" +
39 | " if (!this.el) {\n" +
40 | " var attrs = _.extend({}, _.result(this, 'attributes'));\n" +
41 | " if (this.id) attrs.id = _.result(this, 'id');\n" +
42 | " if (this.className) attrs['class'] = _.result(this, 'className');\n" +
43 | " var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);\n" +
44 | " this.setElement($el, false);\n" +
45 | " } else {\n" +
46 | " this.setElement(_.result(this, 'el'), false);\n" +
47 | " }\n",
48 | "_ensureElement: function() {\n" +
49 | " if (!this.el) {\n" +
50 | " var attrs = _.extend({}, _.result(this, 'attributes'));\n" +
51 | " if (this.id) attrs.id = _.result(this, 'id');\n" +
52 | " if (this.className) attrs['class'] = _.result(this, 'className');\n" +
53 | " var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);\n" +
54 | " this.setElement($el, false);\n" +
55 | " } else {\n" +
56 | " this.setElement(_.result(this, 'el'), false);\n" +
57 | " }\n" +
58 | " this.$el.attr('data-isomorphic', true);\n"
59 | );
60 |
61 | data = data.replace(
62 | "_ensureElement: function() {\n" +
63 | " if (!this.el) {\n" +
64 | " var attrs = _.extend({}, _.result(this, 'attributes'));\n" +
65 | " if (this.id) attrs.id = _.result(this, 'id');\n" +
66 | " if (this.className) attrs['class'] = _.result(this, 'className');\n" +
67 | " var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);\n" +
68 | " this.setElement($el, false);\n" +
69 | " } else {\n" +
70 | " this.setElement(_.result(this, 'el'), false);\n" +
71 | " }\n",
72 | "_ensureElement: function() {\n" +
73 | " if (!this.el) {\n" +
74 | " var attrs = _.extend({}, _.result(this, 'attributes'));\n" +
75 | " if (this.id) attrs.id = _.result(this, 'id');\n" +
76 | " if (this.className) attrs['class'] = _.result(this, 'className');\n" +
77 | " var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);\n" +
78 | " this.setElement($el, false);\n" +
79 | " } else {\n" +
80 | " this.setElement(_.result(this, 'el'), false);\n" +
81 | " }\n" +
82 | " this.$el.attr('data-isomorphic', true);\n"
83 | );
84 |
85 | }
86 |
87 | if(write) {
88 |
89 | fs.writeFileSync(path, data, 'utf8');
90 |
91 | }
92 |
93 | };
--------------------------------------------------------------------------------
/postinstall/overwrite-marionette.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs')
2 |
3 | module.exports = function(path) {
4 |
5 | var data = fs.readFileSync(path, 'utf8'),
6 | write = false;
7 |
8 | if(data.indexOf("this.isomorphic === true;") === -1) {
9 |
10 | write = true;
11 |
12 | data = data.replace(
13 | " render: function() {\n" +
14 | " this._ensureViewIsIntact();\n",
15 | " render: function() {\n\n" +
16 | " if(this.isomorphic) {\n" +
17 | " this._renderTemplate();\n" +
18 | " if(_.isFunction(this.onRenderServerSide)) {\n" +
19 | " this.onRenderServerSide();\n" +
20 | " }\n" +
21 | " return this;\n" +
22 | " }\n\n" +
23 | " this._ensureViewIsIntact();\n"
24 | );
25 |
26 | }
27 |
28 | if(write) {
29 |
30 | fs.writeFileSync(path, data, 'utf8');
31 |
32 | }
33 |
34 | };
--------------------------------------------------------------------------------
/postinstall/postinstall.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs-extra'),
2 | overwriteBackbone = require('./overwrite-backbone'),
3 | overwriteMarionette = require('./overwrite-marionette');
4 |
5 | overwriteBackbone('node_modules/backbone/backbone.js');
6 | // overwriteBackbone('node_modules/backbone.marionette/node_modules/backbone/backbone.js');
7 |
8 | overwriteMarionette('node_modules/backbone.marionette/lib/backbone.marionette.js');
9 | overwriteMarionette('node_modules/backbone.marionette/lib/core/backbone.marionette.js');
10 |
11 | fs.copySync('postinstall/gulp-replace.js', 'node_modules/gulp-replace/index.js');
--------------------------------------------------------------------------------
/postinstall/postinstall.md:
--------------------------------------------------------------------------------
1 | # Gulp replace
2 |
3 |
4 | # Backbone
5 |
6 | The project uses Backbone Marionette 2.4.1 which depends on Backbone 1.1.2, their is a problem in the UMD module code
7 | associated to Backbone because it prevents the project to work with jQuery under Node.
8 |
9 | The problem is that by default Backbone is configured to not use jQuery when its loaded in a Node environment. So to
10 | make it work we have to replace the following in `node_modules/backbone.marionette/node_modules/backbone.js` (line 20) :
11 |
12 | **Note:** This replacement is automatically done by the `npm install` command executing the
13 | `postinstall/overwrite-backbone.js` file.
14 |
15 | ```javascript
16 | var _ = require('underscore');
17 | factory(root, exports, _);
18 | ```
19 |
20 | By this :
21 |
22 | ```javascript
23 | var _ = require('underscore');
24 | var $ = require('jquery');
25 | factory(root, exports, _, $);
26 | ```
--------------------------------------------------------------------------------
/src/data/README.md:
--------------------------------------------------------------------------------
1 | This directory contains JSON files which are read at server start to feed a test SQLite 3 database. It allows to show
2 | how to feed Backbone Models on server side using real data.
3 |
4 | The test database is created using the top famous frameworks registered on
5 | [Backplug](http://backplug.io/?sort=stars "Backplug").
6 |
7 | The tables created are the following :
8 | - frameworks
9 | - owners
10 |
11 | Plus additional custom tables :
12 | - websites
13 |
--------------------------------------------------------------------------------
/src/data/frameworks.json:
--------------------------------------------------------------------------------
1 | [
2 | {
3 | "name" : "backbone.marionette",
4 | "description" : "The Backbone Framework",
5 | "version" : "2.4.2",
6 | "stars" : 6409
7 | },
8 | {
9 | "name" : "rendr",
10 | "description" : "Render your Backbone.js apps on the client and the server, using Node.js.",
11 | "version" : "1.1.2",
12 | "stars" : 3915
13 | },
14 | {
15 | "name" : "chaplin",
16 | "description" : "HTML5 application architecture using Backbone.js",
17 | "version" : "1.0.0",
18 | "stars" : 2935
19 | },
20 | {
21 | "name" : "Backbone-relational",
22 | "description" : "Get and set relations (one-to-one, one-to-many, many-to-one) for Backbone models",
23 | "version" : "0.10.0",
24 | "stars" : 2366
25 | },
26 | {
27 | "name" : "backbone-forms",
28 | "description" : "Form framework for BackboneJS with nested forms, editable lists and validation",
29 | "version" : "0.14.0",
30 | "stars" : 2113
31 | }
32 | ]
--------------------------------------------------------------------------------
/src/js/client/.gitignore:
--------------------------------------------------------------------------------
1 | /bower_components/
2 |
--------------------------------------------------------------------------------
/src/js/client/bootstrap.js:
--------------------------------------------------------------------------------
1 | var ItemView = require('./views/item-view/item-view');
2 |
3 | var itemView = new ItemView(
4 | {
5 | el : '#item-view',
6 | template : false
7 | }
8 | );
9 | itemView.render();
10 |
--------------------------------------------------------------------------------
/src/js/common/views/backbone-view/backbone-view.js:
--------------------------------------------------------------------------------
1 | var Backbone = require('backbone'),
2 | _ = require('underscore'),
3 | tpl = require('./backbone-view.tpl');
4 |
5 | module.exports = Backbone.View.extend(
6 | {
7 | render : function() {
8 |
9 | this.$el.html(tpl);
10 |
11 | }
12 | }
13 | );
--------------------------------------------------------------------------------
/src/js/common/views/backbone-view/backbone-view.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sample Backbone View
4 |
5 |
6 |
7 |
I'm rendered server side ;-)
8 |
9 |
10 |
--------------------------------------------------------------------------------
/src/js/common/views/framework/framework-layout-view.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore'),
2 | Mn = require('backbone.marionette'),
3 | tpl = require('./framework-layout-view.tpl');
4 |
5 | module.exports = Mn.LayoutView.extend(
6 | {
7 | /**
8 | * The CSS classes to associate to the root DOM element of this view.
9 | */
10 | className : 'jumbotron',
11 |
12 | /**
13 | * The Marionette regions which compose this view.
14 | */
15 | regions : {
16 | //ownerRegion : '#owner-region'
17 | },
18 |
19 | /**
20 | * The underscore template used to generate the HTML fragment attached to this view.
21 | */
22 | template : _.template(tpl),
23 |
24 | /**
25 | * Function called just after the view is rendered.
26 | */
27 | onRender : function() {
28 |
29 | // this.sampleRegion.show(
30 | // new Mn.ItemView(
31 | // {
32 | // className : 'well',
33 | // template: _.template('I\'m a Marionette view rendered on server side inside a region !
')
34 | // }
35 | // )
36 | // );
37 |
38 | }
39 | }
40 | );
--------------------------------------------------------------------------------
/src/js/common/views/framework/framework-layout-view.tpl:
--------------------------------------------------------------------------------
1 |
2 | <%= name %>
3 | <%= version %>
4 |
5 |
6 | <%= description %>
7 |
8 |
9 | <%= stars %>
10 |
11 |
--------------------------------------------------------------------------------
/src/js/common/views/item-view/item-view.js:
--------------------------------------------------------------------------------
1 | var Backbone = require('backbone'),
2 | _ = require('underscore'),
3 | Mn = require('backbone.marionette'),
4 | tpl = require('./item-view.tpl');
5 |
6 | /**
7 | * Sample Marionette ItemView rendered on the server side and bound on client side after.
8 | *
9 | * @author Baptiste GAILLARD (baptiste.gaillard@gomoob.com)
10 | */
11 | module.exports = Mn.ItemView.extend(
12 | {
13 | /**
14 | * Events bound to DOM elements of this view.
15 | */
16 | events : {
17 | 'click @ui.counterBtn' : '_onCounterBtnClick'
18 | },
19 |
20 | /**
21 | * The Underscore template used to generate the HTML fragment attached to this view.
22 | *
23 | * @var {String}
24 | */
25 | template : _.template(tpl),
26 |
27 | /**
28 | * Alias to UI elements of this view.
29 | */
30 | ui : {
31 | counter : '#counter',
32 | counterBtn : '#counter-btn'
33 | },
34 |
35 | /**
36 | * Function called just after the view is rendered on server side.
37 | */
38 | onRenderServerSide : function() {
39 |
40 | console.log('Server side rendering ;-)');
41 |
42 | },
43 |
44 | /**
45 | * Function called just afer the view is rendered.
46 | */
47 | onRender : function() {
48 |
49 | console.log('Client side rendering ;-)');
50 |
51 | },
52 |
53 | /**
54 | * Function called when the user clicks on the counter button.
55 | *
56 | * @param {jQuery.Event} clickEvent The click event which triggered a call to this function.
57 | */
58 | _onCounterBtnClick : function(clickEvent) {
59 |
60 | var counter = parseInt(this.ui.counter.text());
61 | this.ui.counter.text(counter + 1);
62 |
63 | }
64 |
65 | }
66 | );
--------------------------------------------------------------------------------
/src/js/common/views/item-view/item-view.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sample Marionette ItemView
4 |
5 |
6 |
7 |
<%= name %>
8 |
<%= description %>
9 |
10 |
I'm rendered on server side ;-)
11 |
12 |
13 | Button bound on client side :
14 |
15 |
19 |
20 |
21 |
22 |
23 |
--------------------------------------------------------------------------------
/src/js/common/views/layout-view/layout-view.js:
--------------------------------------------------------------------------------
1 | var _ = require('underscore'),
2 | Mn = require('backbone.marionette'),
3 | tpl = require('./layout-view.tpl');
4 |
5 | module.exports = Mn.LayoutView.extend(
6 | {
7 | regions : {
8 | sampleRegion : '#sample-region'
9 | },
10 |
11 | template : _.template(tpl),
12 |
13 | onRender : function() {
14 |
15 | this.sampleRegion.show(
16 | new Mn.ItemView(
17 | {
18 | className : 'well',
19 | template: _.template('I\'m a Marionette view rendered on server side inside a region !
')
20 | }
21 | )
22 | );
23 |
24 | }
25 | }
26 | );
--------------------------------------------------------------------------------
/src/js/common/views/layout-view/layout-view.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 | Sample Marionette LayoutView
4 |
5 |
6 |
7 |
I'm rendered server side ;-)
8 |
9 |
10 |
11 |
12 |
--------------------------------------------------------------------------------
/src/js/server/app.tpl:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
backbone.isomorphic
18 | Backbone and Marionette isomorphic Proof Of Concepts
19 |
20 |
21 |
22 |
23 |
26 |
27 |
28 |
31 |
32 |
33 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 |
--------------------------------------------------------------------------------
/src/js/server/controller/framework-controller.js:
--------------------------------------------------------------------------------
1 | var express = require('express'),
2 | router = express.Router(),
3 |
4 | // Business services
5 | frameworkService = require('../service/framework-service'),
6 |
7 | // Views
8 | FrameworkLayoutView = require('../../common/views/framework/framework-layout-view');
9 |
10 | router.get(
11 | '/',
12 | function(req, res) {
13 | res.send('frameworks ;-)');
14 | }
15 | );
16 |
17 | router.get(
18 | '/:id',
19 | function(req, res) {
20 | frameworkService.findById(req.params.id).done(
21 | function(framework) {
22 | (new FrameworkLayoutView({el : '#layout-view', model : framework})).render();
23 | res.send(document.documentElement.outerHTML);
24 | }
25 | );
26 | }
27 | );
28 |
29 | module.exports = router;
--------------------------------------------------------------------------------
/src/js/server/controller/index.js:
--------------------------------------------------------------------------------
1 | var express = require('express'),
2 | router = express.Router();
3 |
4 | router.use('/frameworks', require('./framework-controller'))
5 |
6 | router.get(
7 | '/',
8 | function (req, res) {
9 |
10 | var BackboneView = require('../../common/views/backbone-view/backbone-view');
11 | var ItemView = require('../../common/views/item-view/item-view');
12 | var LayoutView = require('../../common/views/layout-view/layout-view');
13 |
14 | // Test with a simple Backbone.View
15 | var backboneView = new BackboneView({ el : '#backbone-view' });
16 | backboneView.render();
17 |
18 | var frameworkService = require('../service/framework-service');
19 | frameworkService.findById(1).done(function(framework) {
20 |
21 | // Test with a simple Marionette.ItemView
22 | var itemView = new ItemView(
23 | {
24 | el : '#item-view',
25 | model : framework
26 | }
27 | );
28 | itemView.render();
29 |
30 | // Test with a simple Marionette.LayoutView
31 | var layoutView = new LayoutView({ el : '#layout-view' });
32 | layoutView.render();
33 |
34 | res.send(document.documentElement.outerHTML);
35 |
36 | });
37 |
38 | }
39 | );
40 |
41 | module.exports = router;
--------------------------------------------------------------------------------
/src/js/server/dal/data-mapper/framework-data-mapper.js:
--------------------------------------------------------------------------------
1 | var Backbone = require('backbone'),
2 | Promise = require('promise'),
3 | db = require('../../db');
4 |
5 | module.exports = {
6 | findById : function(id) {
7 |
8 | return new Promise(
9 | function(resolve, reject) {
10 |
11 | db.get(
12 | "SELECT * FROM frameworks WHERE rowid = " + id,
13 | function(err, row) {
14 |
15 | if(err) {
16 |
17 | reject(err)
18 |
19 | } else if (row) {
20 |
21 | row.id = row.rowid;
22 | delete row.id;
23 |
24 | resolve(new Backbone.Model(row));
25 |
26 | }
27 |
28 | resolve(null);
29 |
30 | }
31 | );
32 | }
33 | );
34 |
35 | }
36 | };
--------------------------------------------------------------------------------
/src/js/server/db.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs'),
2 | SQLite3 = require('sqlite3').verbose(),
3 | db = new SQLite3.Database(':memory:');
4 |
5 | db.serialize(function() {
6 |
7 | db.run('CREATE TABLE frameworks (name TEXT, description TEXT, version TEXT, stars INTEGER)');
8 |
9 | // Insert frameworks
10 | var statement = db.prepare("INSERT INTO frameworks VALUES (?, ?, ?, ?)");
11 |
12 | JSON.parse(fs.readFileSync('../../data/frameworks.json')).forEach(function(framework) {
13 | statement.run(framework.name, framework.description, framework.version, framework.stars);
14 | });
15 |
16 | statement.finalize();
17 |
18 | });
19 |
20 |
21 | module.exports = db;
--------------------------------------------------------------------------------
/src/js/server/document.js:
--------------------------------------------------------------------------------
1 | // Uncomment this if you want to use domino
2 | var domino = require('domino');
3 | global.window = domino.createWindow(require('./app.tpl'));
4 | global.document = window.document;
5 |
6 | // Uncomment this if you want to use jsdom
7 | // var jsdom = require('jsdom').jsdom;
8 | // global.document = jsdom(require('./app.tpl'), {});
9 | // global.window = document.defaultView;
10 |
11 | module.exports = global.document;
--------------------------------------------------------------------------------
/src/js/server/server.js:
--------------------------------------------------------------------------------
1 | var fs = require('fs');
2 |
3 | require.extensions['.tpl'] = function (module, filename) {
4 |
5 | // FIXME: We should use a cache in production here ?
6 | module.exports = fs.readFileSync(filename, 'utf8');
7 |
8 | };
9 |
10 | require('./db');
11 | require('./document');
12 |
13 | // This has to be done after document creation with Domino or Jsdom because jQuery expects a document to be available to
14 | // be initialized
15 | global.$ = require('jquery');
16 |
17 | // The 'Backbone.isomorphic' parameter allows to indicate that we need server side rendering. This boolean is false in
18 | // a browser environment.
19 | Backbone = require('backbone');
20 | Backbone.isomorphic = true;
21 |
22 | var express = require('express');
23 | var app = express();
24 | app.use('/bundle.js', express.static('../client/bundle.js'));
25 | app.use('/bower_components', express.static('../client/bower_components'));
26 | app.use('/views', express.static('../common/views'));
27 |
28 | app.use(require('./controller'));
29 |
30 | var server = app.listen(3000, function () {
31 |
32 | var host = server.address().address;
33 | var port = server.address().port;
34 |
35 | console.log('Example app listening at http://%s:%s', host, port);
36 |
37 | });
--------------------------------------------------------------------------------
/src/js/server/service/framework-service.js:
--------------------------------------------------------------------------------
1 | var frameworkDataMapper = require('../dal/data-mapper/framework-data-mapper');
2 |
3 | module.exports = {
4 | findById : function(id) {
5 | return frameworkDataMapper.findById(id);
6 | }
7 | };
--------------------------------------------------------------------------------