├── .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 |
24 |
25 |
26 | 27 | 28 |
29 |
30 |
31 | 32 | 33 |
34 |
35 |
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 | }; --------------------------------------------------------------------------------