Add initial browser connector example

This commit is contained in:
Ritchie Martori 2014-02-04 09:04:37 -08:00
parent 8ec7b712e8
commit 60d155353a
9 changed files with 5426 additions and 3940 deletions

View File

@ -81,7 +81,8 @@ module.exports = function(grunt) {
'dist/loopback.js': ['index.js'],
},
options: {
ignore: ['nodemailer', 'passport']
ignore: ['nodemailer', 'passport'],
standalone: 'loopback'
}
}
},

9038
dist/loopback.js vendored

File diff suppressed because it is too large Load Diff

42
example/browser/client.js Normal file
View File

@ -0,0 +1,42 @@
// client app
var app = loopback();
app.dataSource('api', {
connector: loopback.Server,
host: 'localhost',
port: 3000,
base: '/api',
discover: loopback.remoteModels
});
app.dataSource('local', {
connector: loopback.Memory
});
var Color = loopback.getModel('Color');
var LocalColor = app.model('LocalColor', {dataSource: 'local'});
LocalColor.create([
{name: 'red'},
{name: 'green'},
{name: 'blue'}
], function() {
LocalColor.replicate(0, Color, {}, function() {
console.log(arguments);
});
});
// Color.create([
// {name: 'red'},
// {name: 'green'},
// {name: 'blue'}
// ], function() {
// Color.find(function(err, colors) {
// console.log(colors);
// });
// });
// Color.find({where: {name: 'green'}}, function(err, colors) {
// console.log(colors);
// });

View File

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en" ng-app="myApp">
<head>
<meta charset="utf-8">
<title>My AngularJS App</title>
<link rel="stylesheet" href="css/app.css"/>
</head>
<body>
<script src="loopback.js"></script>
<script src="loopback-remote-models.js"></script>
<script src="client.js"></script>
</body>
</html>

View File

@ -1,4 +1,20 @@
var loopback = require('../../');
var app = loopback();
var path = require('path');
app.use(loopback.bundle());
app.use(loopback.static(path.join(__dirname, '..', '..', 'dist')));
app.use(loopback.static(path.join(__dirname)));
app.get('/loopback-remote-models.js', loopback.routes(app));
app.use(loopback.rest());
app.dataSource('db', {
connector: loopback.Memory
});
var Color = app.model('Color', {dataSource: 'db', options: {
trackChanges: true
}});
app.model(Color.getChangeModel());
app.listen(3000);

View File

@ -12,6 +12,7 @@ var datasourceJuggler = require('loopback-datasource-juggler');
loopback.Connector = require('./lib/connectors/base-connector');
loopback.Memory = require('./lib/connectors/memory');
loopback.Mail = require('./lib/connectors/mail');
loopback.Server = require('./lib/connectors/server');
/**
* Types

View File

@ -36,4 +36,4 @@ inherits(Memory, Connector);
* JugglingDB Compatibility
*/
Memory.initialize = JdbMemory.initialize;
Memory.initialize = JdbMemory.initialize;

205
lib/connectors/server.js Normal file
View File

@ -0,0 +1,205 @@
/*!
* Dependencies.
*/
var assert = require('assert')
, loopback = require('../loopback')
, debug = require('debug')
, path = require('path');
/*!
* Export the ServerConnector class.
*/
module.exports = ServerConnector;
/*!
* Create an instance of the connector with the given `settings`.
*/
function ServerConnector(settings) {
this.settings = settings;
}
ServerConnector.initialize = function(dataSource, callback) {
var connector = dataSource.connector = new ServerConnector(dataSource.settings);
connector.dataSource = dataSource;
dataSource.DataAccessObject = function() {}; // unused for this connector
var remoteModels = connector.settings.discover;
if(remoteModels) {
remoteModels.forEach(connector.buildModel.bind(connector));
}
callback();
}
ServerConnector.prototype.invoke = function(ctx, callback) {
var req = ctx.toRequest();
console.log(req);
}
ServerConnector.prototype.createRequest = function(method, args) {
var baseUrl = path.join(this.settings.base || '/');
var route = (method.routes && method.routes[0]) || {path: '/'};
var url = path.join(baseUrl, route.path);
}
ServerConnector.prototype.buildModel = function(remoteModel) {
var modelName = remoteModel.modelName;
var dataSource = this.dataSource;
var connector = this;
var Model = loopback.createModel(
modelName,
remoteModel.properties || {},
remoteModel.settings
);
Model.attachTo(dataSource);
if(!Model.defineMethod) {
Model.defineMethod = function defineMethod(method) {
var scope = method.fullName.indexOf('.prototype.') > -1
? Model.prototype : Model;
scope[method.name] = function() {
console.log(method.name);
var callback = arguments[arguments.length - 1];
var ctx = new Context(
connector.settings.base,
remoteModel,
Model,
method,
arguments
);
if(typeof callback !== 'function') callback = undefined;
connector.invoke(ctx, callback);
};
}
}
remoteModel.methods.forEach(Model.defineMethod.bind(Model));
}
function Context(base, meta, model, method, args) {
this.base = base;
this.meta = meta;
this.model = model;
this.method = method;
this.args = this.mapArgs(args);
}
/**
* Build an http request object from the `context`.
* @return {Object} request
*/
Context.prototype.toRequest = function() {
return {
url: this.url(),
query: this.query(),
method: this.verb(),
body: this.body(),
headers: this.headers()
}
}
Context.prototype.url = function() {
var url = path.join(
this.base,
this.meta.baseRoute.path,
this.route().path
);
// replace url fragments with url params
return url;
}
Context.prototype.query = function() {
var accepts = this.method.accepts;
var queryParams;
var ctx = this;
if(accepts && accepts.length) {
accepts.forEach(function(param) {
var http = param.http || {};
var explicit = http.source === 'query';
var implicit = http.source !== 'body' && http.source !== 'url';
if(explicit || implicit) {
queryParams = queryParams || {};
queryParams[param.arg] = ctx.args[param.arg];
}
});
}
return queryParams;
}
Context.prototype.route = function() {
var routes = this.method.routes;
return routes[0] || {path: '/', verb: 'GET'};
}
Context.prototype.verb = function() {
return this.route().verb.toUpperCase();
}
Context.prototype.body = function() {
var accepts = this.method.accepts;
var body;
var ctx = this;
if(accepts && accepts.length) {
accepts.forEach(function(param) {
var http = param.http || {};
var explicit = http.source === 'body';
if(explicit) {
body = ctx.args[param.arg];
}
});
}
return body;
}
Context.prototype.headers = function() {
return {};
}
Context.prototype.mapArgs = function(args) {
var accepts = this.method.accepts || [];
var args = Array.prototype.slice.call(args);
var result = {};
var supportedSources = ['body', 'form', 'query', 'path'];
accepts.forEach(function(param) {
if(param.http && param.http.source) {
// skip explicit unknown sources
if(supportedSources.indexOf(param.http.source) === -1) return;
}
var val = args.shift();
var type = typeof val;
if(Array.isArray(val)) {
type = 'array';
}
// skip all functions
if(type === 'function') return;
switch(param.type) {
case 'any':
case type:
result[param.arg] = val;
break;
default:
// skip this param
args.unshift(val);
break;
}
});
return result;
}

44
lib/middleware/routes.js Normal file
View File

@ -0,0 +1,44 @@
/*!
* Module dependencies.
*/
var loopback = require('../loopback');
/**
* Export the middleware.
*/
module.exports = models;
/**
* Return a script that defines all remote models.
*/
function models(app) {
return function (req, res, next) {
var script = 'window.loopback.remoteModels = ';
var models = [];
app.handler('rest').adapter.getClasses().forEach(function(c) {
if (!c.ctor) {
// Skip classes that don't have a shared ctor
// as they are not LoopBack models
console.error('Skipping %j as it is not a LoopBack model', name);
return;
}
models.push(toJSON(c));
});
res.send(script + JSON.stringify(models, null, 2));
}
}
function toJSON(sharedClass) {
var model = loopback.getModel(sharedClass.name);
return {
modelName: model.modelName,
settings: model.settings,
properties: model.properties,
baseRoute: sharedClass.routes[0],
methods: sharedClass.methods
};
}