From bf520d155b14c89f6db8c6cbc21651dfec7aaa92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Tue, 10 Jun 2014 11:13:43 +0200 Subject: [PATCH 1/3] Rework models to use the new 2.0 layout Split model definition and model configuration. - `models/` is a require()-able component providing all shared models. - `html5/models` is another require()-able component providing html5-specific models like LocalTodo. - `api/models.json` and `html5/models.json` contain data-source configuration for the loopback apps. Modify package.json to track github branch 2.0 of both loopback and loopback-boot. --- api/app.api.js | 8 ++-- api/models.json | 8 ++++ html5/app.html5.js | 4 ++ html5/boot/client-models.js | 11 ----- html5/client.models.json | 8 ---- html5/configure.js | 1 - html5/datasources.json | 2 +- html5/datasources.local.js | 2 +- html5/models.json | 11 +++++ html5/models/index.js | 1 + html5/models/local-todo.js | 4 ++ html5/models/local-todo.json | 4 ++ models.json | 36 ---------------- models/index.js | 2 + models/todo.js | 80 ++++++++++++++++++------------------ models/todo.json | 19 +++++++++ models/user.js | 3 ++ models/user.json | 19 +++++++++ package.json | 4 +- 19 files changed, 122 insertions(+), 105 deletions(-) create mode 100644 api/models.json delete mode 100644 html5/boot/client-models.js delete mode 100644 html5/client.models.json create mode 100644 html5/models.json create mode 100644 html5/models/index.js create mode 100644 html5/models/local-todo.js create mode 100644 html5/models/local-todo.json delete mode 100644 models.json create mode 100644 models/index.js create mode 100644 models/todo.json create mode 100644 models/user.js create mode 100644 models/user.json diff --git a/api/app.api.js b/api/app.api.js index 4447d90..909ceba 100644 --- a/api/app.api.js +++ b/api/app.api.js @@ -4,13 +4,13 @@ var loopback = require('loopback'); var explorer = require('loopback-explorer'); var boot = require('loopback-boot'); +// model definitions +require('../models'); + // server var server = module.exports = loopback(); -boot(server, { - appRootDir: __dirname, - modelsRootDir: path.resolve(__dirname, '..'), -}); +boot(server, __dirname); // middleware server.use(loopback.logger('dev')); diff --git a/api/models.json b/api/models.json new file mode 100644 index 0000000..ab1dc2f --- /dev/null +++ b/api/models.json @@ -0,0 +1,8 @@ +{ + "Todo": { + "dataSource": "db" + }, + "user": { + "dataSource": "db" + } +} diff --git a/html5/app.html5.js b/html5/app.html5.js index dfb6576..58c0a9b 100644 --- a/html5/app.html5.js +++ b/html5/app.html5.js @@ -3,6 +3,10 @@ var LOCAL_CONFIG = require('local.config'); var loopback = require('loopback'); var boot = require('loopback-boot'); +// model definitions +require('../models'); +require('./models/index'); + // loopback client var client = exports.client = loopback(); boot(client); diff --git a/html5/boot/client-models.js b/html5/boot/client-models.js deleted file mode 100644 index 37b5d52..0000000 --- a/html5/boot/client-models.js +++ /dev/null @@ -1,11 +0,0 @@ -var loopback = require('loopback'); - -// TODO(bajtos) This is a temporary workaround until we have -// strongloop/loopback-example-full-stack#13 - -module.exports = function(app) { - var clientModels = require('../client.models.json'); - for (var name in clientModels) { - app.model(name, clientModels[name]); - } -}; diff --git a/html5/client.models.json b/html5/client.models.json deleted file mode 100644 index 226454e..0000000 --- a/html5/client.models.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "LocalTodo": { - "dataSource": "local", - "options": { - "base": "Todo" - } - } -} diff --git a/html5/configure.js b/html5/configure.js index 521f8bc..6fbba3c 100644 --- a/html5/configure.js +++ b/html5/configure.js @@ -87,7 +87,6 @@ function createBundle(env, global, cb) { try { boot.compileToBrowserify({ appRootDir: __dirname, - modelsRootDir: path.resolve(__dirname, '..'), env: env }, b); } catch(err) { diff --git a/html5/datasources.json b/html5/datasources.json index 5ef7a4a..b26ed72 100644 --- a/html5/datasources.json +++ b/html5/datasources.json @@ -1,5 +1,5 @@ { - "db": { + "remote": { "connector": "remote" }, "local": { diff --git a/html5/datasources.local.js b/html5/datasources.local.js index b578bc0..23a28e4 100644 --- a/html5/datasources.local.js +++ b/html5/datasources.local.js @@ -1,7 +1,7 @@ var LOCAL_CONFIG = require('local.config'); module.exports = { - db: { + remote: { url: LOCAL_CONFIG.serverInfo.url } }; diff --git a/html5/models.json b/html5/models.json new file mode 100644 index 0000000..a868f13 --- /dev/null +++ b/html5/models.json @@ -0,0 +1,11 @@ +{ + "Todo": { + "dataSource": "remote" + }, + "LocalTodo": { + "dataSource": "local" + }, + "user": { + "dataSource": "remote" + } +} diff --git a/html5/models/index.js b/html5/models/index.js new file mode 100644 index 0000000..b970c7c --- /dev/null +++ b/html5/models/index.js @@ -0,0 +1 @@ +exports.LocalTodo = require('./local-todo'); diff --git a/html5/models/local-todo.js b/html5/models/local-todo.js new file mode 100644 index 0000000..134764b --- /dev/null +++ b/html5/models/local-todo.js @@ -0,0 +1,4 @@ +var loopback = require('loopback'); + +var LocalTodo = loopback.createModel(require('./local-todo.json')); +module.exports = LocalTodo; diff --git a/html5/models/local-todo.json b/html5/models/local-todo.json new file mode 100644 index 0000000..5f93c88 --- /dev/null +++ b/html5/models/local-todo.json @@ -0,0 +1,4 @@ +{ + "name": "LocalTodo", + "base": "Todo" +} diff --git a/models.json b/models.json deleted file mode 100644 index d1d94ec..0000000 --- a/models.json +++ /dev/null @@ -1,36 +0,0 @@ -{ - "Todo": { - "options": { - "trackChanges": true, - "base": "DataModel" - }, - "properties": { - "id": { - "id": true, - "type": "string" - }, - "title": "string", - "completed": { - "type": "boolean", - "default": false - }, - "created": { - "type": "number" - } - }, - "dataSource": "db" - }, - "user": { - "options": { - "base": "User" - }, - "properties": { - "title": "string", - "done": { - "type": "boolean", - "default": false - } - }, - "dataSource": "db" - } -} diff --git a/models/index.js b/models/index.js new file mode 100644 index 0000000..096a804 --- /dev/null +++ b/models/index.js @@ -0,0 +1,2 @@ +exports.Todo = require('./todo'); +exports.User = require('./user'); diff --git a/models/todo.js b/models/todo.js index 583b27f..502acc4 100644 --- a/models/todo.js +++ b/models/todo.js @@ -1,47 +1,45 @@ var loopback = require('loopback'); var async = require('async'); -module.exports = function(app) { - var Todo = app.models.Todo; - - Todo.definition.properties.created.default = Date.now; - - Todo.beforeSave = function(next, model) { - if (!model.id) model.id = 't-' + Math.floor(Math.random() * 10000).toString(); - next(); - }; - - Todo.stats = function(filter, cb) { - var stats = {}; - cb = arguments[arguments.length - 1]; - var Todo = this; - - async.parallel([ - countComplete, - count - ], function(err) { - if (err) return cb(err); - stats.remaining = stats.total - stats.completed; - cb(null, stats); - }); +var Todo = module.exports = loopback.createModel(require('./todo.json')); + +Todo.definition.properties.created.default = Date.now; - function countComplete(cb) { - Todo.count({completed: true}, function(err, count) { - stats.completed = count; - cb(err); - }); - } - - function count(cb) { - Todo.count(function(err, count) { - stats.total = count; - cb(err); - }); - } - }; - - loopback.remoteMethod(Todo.stats, { - accepts: {arg: 'filter', type: 'object'}, - returns: {arg: 'stats', type: 'object'} +Todo.beforeSave = function(next, model) { + if (!model.id) model.id = 't-' + Math.floor(Math.random() * 10000).toString(); + next(); +}; + +Todo.stats = function(filter, cb) { + var stats = {}; + cb = arguments[arguments.length - 1]; + var Todo = this; + + async.parallel([ + countComplete, + count + ], function(err) { + if (err) return cb(err); + stats.remaining = stats.total - stats.completed; + cb(null, stats); }); + + function countComplete(cb) { + Todo.count({completed: true}, function(err, count) { + stats.completed = count; + cb(err); + }); + } + + function count(cb) { + Todo.count(function(err, count) { + stats.total = count; + cb(err); + }); + } }; + +loopback.remoteMethod(Todo.stats, { + accepts: {arg: 'filter', type: 'object'}, + returns: {arg: 'stats', type: 'object'} +}); diff --git a/models/todo.json b/models/todo.json new file mode 100644 index 0000000..8d0e919 --- /dev/null +++ b/models/todo.json @@ -0,0 +1,19 @@ +{ + "name": "Todo", + "base": "PersistedModel", + "trackChanges": true, + "properties": { + "id": { + "id": true, + "type": "string" + }, + "title": "string", + "completed": { + "type": "boolean", + "default": false + }, + "created": { + "type": "number" + } + } +} diff --git a/models/user.js b/models/user.js new file mode 100644 index 0000000..367cfc6 --- /dev/null +++ b/models/user.js @@ -0,0 +1,3 @@ +var loopback = require('loopback'); + +var User = module.exports = loopback.createModel(require('./user.json')); diff --git a/models/user.json b/models/user.json new file mode 100644 index 0000000..7b1b443 --- /dev/null +++ b/models/user.json @@ -0,0 +1,19 @@ +{ + "name": "user", + "base": "PersistedModel", + "trackChanges": true, + "properties": { + "id": { + "id": true, + "type": "string" + }, + "title": "string", + "completed": { + "type": "boolean", + "default": false + }, + "created": { + "type": "number" + } + } +} diff --git a/package.json b/package.json index 6f24a5f..b8b53d6 100644 --- a/package.json +++ b/package.json @@ -37,11 +37,11 @@ }, "dependencies": { "async": "~0.7.0", - "loopback": "2.0.0-beta3", + "loopback": "strongloop/loopback#2.0", "loopback-explorer": "~1.1.0", "loopback-connector-mongodb": "^1.2.4", "loopback-datasource-juggler": "2.0.0-beta1", - "loopback-boot": "strongloop/loopback-boot", + "loopback-boot": "strongloop/loopback-boot#2.0", "ejs": "^1.0.0", "morgan": "^1.1.1", "compression": "^1.0.3" From c46f548fff378c5c4b98e0cf08e891c65c217461 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 12 Jun 2014 16:07:31 +0200 Subject: [PATCH 2/3] Refactor out `lbclient/` from `html5/` Move all loopback-related code out of `html5/app.html5.js` into a new component `lbclient/`. --- gulpfile.js | 21 +++++++-- html5/app.html5.js | 9 +--- html5/configure.js | 47 ++++++++++--------- lbclient/.gitignore | 1 + lbclient/app.client.js | 9 ++++ {html5 => lbclient}/boot/replication.js | 0 lbclient/configure.js | 54 ++++++++++++++++++++++ {html5 => lbclient}/datasources.json | 0 {html5 => lbclient}/datasources.local.js | 0 {html5 => lbclient}/models.json | 0 {html5 => lbclient}/models/index.js | 0 {html5 => lbclient}/models/local-todo.js | 0 {html5 => lbclient}/models/local-todo.json | 0 lbclient/package.json | 4 ++ web/views/footer.ejs | 1 + 15 files changed, 112 insertions(+), 34 deletions(-) create mode 100644 lbclient/.gitignore create mode 100644 lbclient/app.client.js rename {html5 => lbclient}/boot/replication.js (100%) create mode 100644 lbclient/configure.js rename {html5 => lbclient}/datasources.json (100%) rename {html5 => lbclient}/datasources.local.js (100%) rename {html5 => lbclient}/models.json (100%) rename {html5 => lbclient}/models/index.js (100%) rename {html5 => lbclient}/models/local-todo.js (100%) rename {html5 => lbclient}/models/local-todo.json (100%) create mode 100644 lbclient/package.json diff --git a/gulpfile.js b/gulpfile.js index 3192ffc..0ebd928 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -24,6 +24,7 @@ gulp.task('run', function(cb) { '**/node_modules/**', '*/build/**', '**/test/**', + '**/*.bundle.*', '.*' // .git, .idea, etc. ], watch: __dirname, @@ -68,7 +69,7 @@ function findAndBuild(packageName, cb) { writeGlobalConfigModule(); // build each - async.each(packages, build, cb); + async.eachSeries(packages, build, cb); } var packageCache; @@ -97,13 +98,27 @@ function listPackages() { }) .map(function(filePath) { return path.basename(path.dirname(filePath)); + }) + .sort(function(a, b) { + // temporary hack - ensure lbclient is built before html5 + if (a === 'lbclient') return -1; + if (b === 'lbclient') return 1; + if (a < b) return -1; + if (a > b) return 1; + return 0; }); } function build(package, cb) { - var buildConfig = config.build[package]; console.log('building', package); - configure(package, 'build', cb); + configure(package, 'build', function(err) { + if (err) { + console.error('%s build failed', package, err.stack); + } else { + console.log('%s was built', package); + } + cb(err); + }); } function configure(package, scope, cb) { diff --git a/html5/app.html5.js b/html5/app.html5.js index 58c0a9b..d0a4fc7 100644 --- a/html5/app.html5.js +++ b/html5/app.html5.js @@ -1,15 +1,8 @@ // dependencies var LOCAL_CONFIG = require('local.config'); -var loopback = require('loopback'); -var boot = require('loopback-boot'); - -// model definitions -require('../models'); -require('./models/index'); // loopback client -var client = exports.client = loopback(); -boot(client); +var client = exports.client = require('lbclient'); // angular.js dependencies require('./bower_components/angular/angular.js'); diff --git a/html5/configure.js b/html5/configure.js index 6fbba3c..1f1ae63 100644 --- a/html5/configure.js +++ b/html5/configure.js @@ -1,11 +1,8 @@ -var gulp = require('gulp'); var async = require('async'); var path = require('path'); var pkg = require('./package.json'); var fs = require('fs'); var browserify = require('browserify'); -var sh = require('shelljs'); -var boot = require('loopback-boot'); var buildDir = path.resolve(__dirname, 'build'); @@ -47,7 +44,9 @@ exports.global = function(env, global) { global.bundle += '.js'; global.html5Views = path.join(__dirname, 'views'); global.html5Bundle = path.join(buildDir, global.bundle); + global.clientBundle = path.join(buildDir, 'client.' + global.bundle); global.bundleURL = '/' + global.bundle; + global.clientURL = '/client.' + global.bundle; } exports.local = function configure(env, global, local) { @@ -65,17 +64,12 @@ exports.local = function configure(env, global, local) { } exports.build = function(env, global, local, cb) { - async.waterfall([ - function createBuildDir(next) { - fs.exists(buildDir, function(yes) { - if (yes) - next(); - else - fs.mkdir(buildDir, next); - }); - }, + async.parallel([ function(next) { createBundle(env, global, next); + }, + function(next) { + copyClientBundle(global, next); } ], cb); }; @@ -83,15 +77,7 @@ exports.build = function(env, global, local, cb) { function createBundle(env, global, cb) { var b = browserify({basedir: __dirname}); b.add('./' + pkg.main); - - try { - boot.compileToBrowserify({ - appRootDir: __dirname, - env: env - }, b); - } catch(err) { - return cb(err); - } + b.exclude('lbclient'); var bundleDir = path.dirname(global.html5Bundle); if (!fs.existsSync(bundleDir)) @@ -99,7 +85,7 @@ function createBundle(env, global, cb) { var out = fs.createWriteStream(global.html5Bundle); - if(!isDev(env)) { + if (!isDev(env)) { b.transform({ global: true }, 'uglifyify'); @@ -109,12 +95,27 @@ function createBundle(env, global, cb) { // TODO(bajtos) debug should be always true, the sourcemaps should be // saved to a standalone file when !isDev(env) debug: isDev(env) - }).pipe(out); + }) + .on('error', cb) + .pipe(out); out.on('error', cb); out.on('close', cb); } +function copyClientBundle(global, cb) { + var clientBundle = require.resolve('../lbclient/browser.bundle.js'); + var client = fs.createReadStream(clientBundle); + + var out = fs.createWriteStream(global.clientBundle); + + client + .on('error', cb) + .pipe(out) + .on('error', cb) + .on('close', cb); +} + function isDev(env) { return ~['debug', 'development', 'test'].indexOf(env); } diff --git a/lbclient/.gitignore b/lbclient/.gitignore new file mode 100644 index 0000000..9f60abc --- /dev/null +++ b/lbclient/.gitignore @@ -0,0 +1 @@ +browser.bundle.js diff --git a/lbclient/app.client.js b/lbclient/app.client.js new file mode 100644 index 0000000..ddc6006 --- /dev/null +++ b/lbclient/app.client.js @@ -0,0 +1,9 @@ +var loopback = require('loopback'); +var boot = require('loopback-boot'); + +// model definitions +require('../models'); +require('./models/index'); + +var client = module.exports = loopback(); +boot(client); diff --git a/html5/boot/replication.js b/lbclient/boot/replication.js similarity index 100% rename from html5/boot/replication.js rename to lbclient/boot/replication.js diff --git a/lbclient/configure.js b/lbclient/configure.js new file mode 100644 index 0000000..8c93d26 --- /dev/null +++ b/lbclient/configure.js @@ -0,0 +1,54 @@ +var path = require('path'); +var pkg = require('./package.json'); +var fs = require('fs'); +var browserify = require('browserify'); +var boot = require('loopback-boot'); + +exports.global = function(env, global) { +}; + +exports.local = function configure(env, global, local) { + // NOTE: this config will be available in the browser + local.serverInfo = { + api: global.api, + url: global.api.protocol + + '://' + + global.api.host + + ':' + + global.api.port + + global.api.root + }; + local.routes = global.routes; +}; + +exports.build = function(env, global, local, cb) { + var b = browserify({ basedir: __dirname }); + b.require('./' + pkg.main, { expose: 'lbclient' }); + + try { + boot.compileToBrowserify({ + appRootDir: __dirname, + env: env + }, b); + } catch(err) { + return cb(err); + } + + var bundlePath = path.resolve(__dirname, 'browser.bundle.js'); + var out = fs.createWriteStream(path.resolve(__dirname, bundlePath)); + + b.bundle({ + // TODO(bajtos) debug should be always true, the sourcemaps should be + // saved to a standalone file when !isDev(env) + debug: isDev(env) + }) + .on('error', cb) + .pipe(out); + + out.on('error', cb); + out.on('close', cb); +}; + +function isDev(env) { + return ~['debug', 'development', 'test'].indexOf(env); +} diff --git a/html5/datasources.json b/lbclient/datasources.json similarity index 100% rename from html5/datasources.json rename to lbclient/datasources.json diff --git a/html5/datasources.local.js b/lbclient/datasources.local.js similarity index 100% rename from html5/datasources.local.js rename to lbclient/datasources.local.js diff --git a/html5/models.json b/lbclient/models.json similarity index 100% rename from html5/models.json rename to lbclient/models.json diff --git a/html5/models/index.js b/lbclient/models/index.js similarity index 100% rename from html5/models/index.js rename to lbclient/models/index.js diff --git a/html5/models/local-todo.js b/lbclient/models/local-todo.js similarity index 100% rename from html5/models/local-todo.js rename to lbclient/models/local-todo.js diff --git a/html5/models/local-todo.json b/lbclient/models/local-todo.json similarity index 100% rename from html5/models/local-todo.json rename to lbclient/models/local-todo.json diff --git a/lbclient/package.json b/lbclient/package.json new file mode 100644 index 0000000..f00713f --- /dev/null +++ b/lbclient/package.json @@ -0,0 +1,4 @@ +{ + "name": "client", + "main": "app.client.js" +} diff --git a/web/views/footer.ejs b/web/views/footer.ejs index d2e46c1..5b66f24 100644 --- a/web/views/footer.ejs +++ b/web/views/footer.ejs @@ -1 +1,2 @@ + From b2ec426bb27d65e065910ee288271c73bc5dee43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miroslav=20Bajto=C5=A1?= Date: Thu, 12 Jun 2014 16:19:47 +0200 Subject: [PATCH 3/3] Refactor `window.connected()` to a service Modify the `client/` to provide a `network` API with a single property: `isConnected`. --- html5/app.html5.js | 1 + html5/controllers/todo.ctrl.js | 8 ++++---- lbclient/boot/replication.js | 20 ++++++++++++-------- 3 files changed, 17 insertions(+), 12 deletions(-) diff --git a/html5/app.html5.js b/html5/app.html5.js index d0a4fc7..adad111 100644 --- a/html5/app.html5.js +++ b/html5/app.html5.js @@ -20,6 +20,7 @@ var app = module.exports = angular.module('app', dependencies); // providers app.value('Todo', client.models.LocalTodo); app.value('sync', client.sync); +app.value('network', client.network); // setup controllers // must require controllers in order for browserify diff --git a/html5/controllers/todo.ctrl.js b/html5/controllers/todo.ctrl.js index 789cb8e..058240f 100644 --- a/html5/controllers/todo.ctrl.js +++ b/html5/controllers/todo.ctrl.js @@ -2,7 +2,7 @@ module.exports = TodoCtrl; var app = require('../app.html5'); var async = require('async'); -function TodoCtrl($scope, $routeParams, $filter, Todo, $location, sync) { +function TodoCtrl($scope, $routeParams, $filter, Todo, $location, sync, network) { var todos = $scope.todos = []; $scope.newTodo = ''; @@ -97,16 +97,16 @@ function TodoCtrl($scope, $routeParams, $filter, Todo, $location, sync) { }; $scope.connected = function() { - return window.connected(); + return network.isConnected; }; $scope.connect = function() { - window.isConnected = true; + network.isConnected = true; sync(); }; $scope.disconnect = function() { - window.isConnected = false; + network.isConnected = false; }; Todo.on('conflicts', function(conflicts) { diff --git a/lbclient/boot/replication.js b/lbclient/boot/replication.js index 49ddce4..750e7d2 100644 --- a/lbclient/boot/replication.js +++ b/lbclient/boot/replication.js @@ -11,9 +11,20 @@ module.exports = function(client) { var LocalTodo = client.models.LocalTodo; var RemoteTodo = client.models.Todo; + client.network = { + _isConnected: true, + get isConnected() { + console.log('isConnected?', this._isConnected); + return this._isConnected; + }, + set isConnected(value) { + this._isConnected = value; + } + }; + // setup model replication function sync(cb) { - if (window.connected()) { + if (client.network.isConnected) { RemoteTodo.replicate(LocalTodo, function() { LocalTodo.replicate(RemoteTodo, cb); }); @@ -25,11 +36,4 @@ module.exports = function(client) { LocalTodo.on('deleted', sync); client.sync = sync; - - window.isConnected = true; - - window.connected = function connected() { - console.log('isConnected?', window.isConnected); - return window.isConnected; - }; };