diff --git a/.gitignore b/.gitignore
index 3a28069..996423a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -10,10 +10,10 @@ npm-debug.log
test/data
# Ignore example data
-example/node_modules
-example/npm-debug.log
example/www_static/css/lib
example/www_static/js/lib
+example/www_logs
+example/lib_app
# Ignore binary files
*.o
diff --git a/TODO.md b/TODO.md
new file mode 100644
index 0000000..cd08549
--- /dev/null
+++ b/TODO.md
@@ -0,0 +1,20 @@
+TODO
+=========
+
+Small todo list
+
+## angular
+
+* add downloads
+* (paging)
+* field types
+* validation
+* tabs for child entities
+* i18n
+
+## server
+
+* ldap support
+* route page
+* keyyed sub list
+* menu
\ No newline at end of file
diff --git a/example/example-config.json b/example/example-config.json
new file mode 100644
index 0000000..b0fdcda
--- /dev/null
+++ b/example/example-config.json
@@ -0,0 +1,45 @@
+{
+ "winston": {
+ "console": {
+ "level": "debug",
+ "silent": false,
+ "colorize": true,
+ "timestamp": null
+ },
+ "file": {
+ "level": "debug",
+ "silent": false,
+ "colorize": false,
+ "timestamp": null,
+ "json": false,
+ "filename": "www_logs/server.log"
+ }
+ },
+ "server": {
+ "httpPort": 8008,
+ "httpTrustProxy": true,
+ "sessionSecret": "tcrudIDKEy",
+ "sessionTTL": "14 * 24 * 60 * 60"
+ },
+ "application": {
+ "name": "TCrud Example Server",
+ "index": {
+ "pageTitle": "TCrud Example Server",
+ "pageKeywords": "node,crud,api,json,xml,views"
+ }
+ },
+ "options": {
+ "static": {
+ "maxAge": 86400000
+ },
+ "cookieParser": {
+ "secretKey": "test88test"
+ },
+ "rss": {
+ "link": "http://localhost:8008",
+ "author": "TCrud",
+ "options": {
+ }
+ }
+ }
+}
diff --git a/example/example.js b/example/example.js
new file mode 100644
index 0000000..9890e55
--- /dev/null
+++ b/example/example.js
@@ -0,0 +1,61 @@
+'use strict';
+
+var config = require('./example-config.json');
+var favicon = require('static-favicon');
+var cookieParser = require('cookie-parser')
+var path = require('path');
+var winston = require('winston');
+winston.loggers.add('main',config.winston);
+var log = winston.loggers.get('main');
+var expressWinston = require('express-winston');
+var tcrud = require('../lib/node-ff-tcrud');
+var fs = require('fs');
+
+
+// PHASE_1: load extra plugins
+//tcrud.setup.pluginLoad(new MyPlugin());
+require('./lib/pg-moviedb').load(tcrud);
+require('./lib/pg-pagila').load(tcrud);
+
+// PHASE_2: enable plugins
+tcrud.setup.pluginEnableAll();
+
+// PHASE_3: start config
+tcrud.setup.phaseConfig();
+tcrud.config.getRootTEntity().tmeta.tplugin.formatXML.tslug = 'xml_is_free';
+tcrud.config.getRootTEntity().tmeta.tplugin.formatCSV.tslug = 'csv4all';
+var tPostgresDB = tcrud.config.createTEntityNode(tcrud.config.getRootTEntity(),'pg');
+var tMongoose = tcrud.config.createTEntityNode(tcrud.config.getRootTEntity(),'mongoose');
+require('./lib/pg-moviedb').setup(tcrud,tPostgresDB);
+require('./lib/pg-pagila').setup(tcrud,tPostgresDB);
+//require('./lib/mongoose-blog').setup(tcrud,tMongoose);
+
+// PHASE_4: finalize config
+tcrud.setup.phaseServer();
+//tcrud.setup.expressSimple(); or complex;
+
+var server = tcrud.setup.expressCreate();
+server.use(expressWinston.logger({
+ transports: [new winston.transports.Console({json: false,colorize: true})],
+ meta: false,
+ expressFormat: true
+}));
+
+var buildOptions = {
+ viewsDir: path.join(__dirname, 'www_views')
+}
+tcrud.setup.expressBuild(buildOptions);
+
+server.use(favicon());
+server.use(cookieParser(config.options.cookieParser.secretKey));
+
+//server.get('/ui/thtml/*', tcrud.setup.express.renderTemplatePath('thtml/'));
+
+server.use(expressWinston.errorLogger({
+ transports: [new winston.transports.Console({json: false,colorize: true})]
+}));
+
+tcrud.setup.expressListen();
+
+//PHASE_6: post boot to from self.
+tcrud.setup.phaseServerUp();
diff --git a/example/lib/ldapjs-example.js b/example/lib/ldapjs-example.js
new file mode 100644
index 0000000..830f52c
--- /dev/null
+++ b/example/lib/ldapjs-example.js
@@ -0,0 +1,26 @@
+var ldap = require('ldapjs');
+
+module.exports = {
+ setup: setup
+};
+
+function setup(tcrud,tcrudModel) {
+
+ var client = ldap.createClient({
+ url: 'ldap://127.0.0.1:389'
+ });
+
+ // Create backend with id and uri
+ tcrud.plugin.backend.ldapjs.registrate('ldapjs/main',client);
+
+ // Create tcrud models
+ var tc = tcrud.config;
+ var t = tc.createTEntityNode(tcrudModel,'ldapjs');
+ t.tmeta.tmodel.tbackend = 'ldapjs/main';
+
+ // Define model and columns
+ var tUser = tc.createTEntity(t,'cn=foo, o=example','cn');
+ var tUserId = tc.createTField(tUser,'uid');
+ var tUserName = tc.createTField(tUser,'sn');
+ var tUserEmail = tc.createTField(tUser,'email');
+}
diff --git a/example/lib/mongoose-blog.js b/example/lib/mongoose-blog.js
new file mode 100644
index 0000000..db6db04
--- /dev/null
+++ b/example/lib/mongoose-blog.js
@@ -0,0 +1,153 @@
+var mongoose = require('mongoose');
+var log = require('winston').loggers.get('main');
+var tcrud = require('../../lib/node-ff-tcrud');
+
+module.exports = {
+ setup: setup
+};
+
+function setup(tcrud,tcrudModel) {
+
+ var mongoUrl = 'mongodb://localhost:27017/blog';
+ var mongoOptions = {
+ db: {
+ fsync: false,
+ journal: false,
+ native_parser: true,
+ forceServerObjectId: true
+ },
+ server: {
+ poolSize: 4,
+ socketOptions: {
+ connectTimeoutMS: 500,
+ keepAlive: 1,
+ auto_reconnect: true
+ }
+ }
+ };
+
+ log.info('Connecting to: ' + mongoUrl);
+ var conn = mongoose.createConnection(mongoUrl,mongoOptions);
+
+ tcrud.plugin.backend.mongoose.registrate('mongoose/blog',conn);
+ conn.model('blog-state', modelSchemaBlogState, 'blog_state');
+
+ var tc = tcrud.config;
+ var crudAdmin = tc.createTEntityNode(tcrudModel,'admin');
+ crudAdmin.troles.push('admin');
+
+ var crudAdminModels = tcrud.plugin.backend.mongoose.buildTEntityModels(conn,crudAdmin);
+ log.info('crud admin models created: '+crudAdminModels.length);
+ crudAdminModels.forEach(function(model) {
+ model.tmeta.tmodel.tbackend = 'mongoose/blog';
+ });
+}
+
+var modelMetaBlogState = {
+ name: {
+ type: String,
+ trim: true,
+ index: { unique: true },
+ tfield: {
+ tvalidate: { io: 'string' },
+ },
+ },
+ type: {
+ type: String,
+ trim: true,
+ index: { unique: false },
+ tfield: {
+ tvalidate: { io: 'string' },
+ },
+ },
+ value: {
+ type: String,
+ trim: true,
+ tfield: {
+ xtype: 'textarea',
+ tvalidate: { io: 'string' },
+ },
+ },
+ description: {
+ type: String,
+ trim: true,
+ tfield: {
+ ttype: 'textarea',
+ tvalidate: { io: 'string' },
+ },
+ },
+ changed_date: {
+ type: Date,
+ default: Date.now,
+ tfield: {
+ tlist: { tenable: false },
+ },
+ },
+ created_date: {
+ type: Date,
+ default: Date.now,
+ tfield: {
+ tlist: { tenable: false },
+ },
+ }
+};
+
+var modelSchemaBlogState = new mongoose.Schema(modelMetaBlogState);
+modelSchemaBlogState.statics = tcrud.plugin.backend.mongoose.buildStaticsModelValidated(modelMetaBlogState,modelSchemaBlogState, {
+ findLastChangedLimit5: function (callback) {
+ log.debug(modelBackend+'.findLastChangedLimit5');
+ this.find({}).sort('-changed_date').limit(5).exec(callback);
+ },
+ findOneByName: function (name, callback) {
+ log.debug(modelBackend+'.findByName name='+name);
+ this.findOne({name:name}).exec(callback);
+ },
+ ensureExcists: function (name, type, defaultValue, description, callback) {
+ this.findOneByName(name, function(err, xprop) {
+ if (err) {
+ callback(err);
+ return;
+ }
+ if (xprop == null) {
+ log.debug(modelBackend+'.getByName create name='+name+' defaultValue='+defaultValue);
+ var model = mongoose.model('blog-state');
+ xprop = new model();
+ xprop.name = name;
+ xprop.type = type;
+ xprop.value = defaultValue;
+ xprop.description = description;
+ xprop.save(function(err,xprop) {
+ if (callback) {
+ callback(err, xprop);
+ }
+ });
+ } else {
+ log.debug(modelBackend+'.getByName fetched name='+name);
+ if (callback) {
+ callback(null, xprop);
+ }
+ }
+ });
+ },
+ setByName: function (name, value, callback) {
+ this.findOneByName(name, function(err, xprop) {
+ if (err) { throw err }
+ log.debug(modelBackend+'.setByName name='+name+' valueNew='+value+' valueOld='+xprop.value);
+ xprop.value = value;
+ xprop.save(function(err) {
+ callback(err, xprop);
+ });
+ });
+ },
+ incByName: function (name, callback) {
+ this.findOneByName(name, function(err, xprop) {
+ if (err) { throw err }
+ xprop.value++;
+ log.debug(modelBackend+'.incByName name='+name+' value='+xprop.value);
+ xprop.save(function(err) {
+ callback(err, xprop);
+ });
+ });
+ },
+});
+
diff --git a/example/lib/pg-moviedb.js b/example/lib/pg-moviedb.js
new file mode 100644
index 0000000..9cf43c4
--- /dev/null
+++ b/example/lib/pg-moviedb.js
@@ -0,0 +1,60 @@
+var pgDB = require('pg');
+var pgDBNamed = require('node-postgres-named');
+
+module.exports = {
+ setup: setup,
+ load: load
+};
+
+function load(tcrud) {
+
+ // Create backend with id and uri
+ tcrud.backend.database.loadPostgres('pg/moviedb','postgres://postgres:postgresql@localhost/moviedb',pgDB,pgDBNamed);
+}
+
+function setup(tcrud,tcrudModel) {
+
+ // Create tcrud models
+ var tc = tcrud.config;
+ var t = tc.createTEntityNode(tcrudModel,'moviedb');
+ t.tmeta.tmodel.tbackend = 'pg/moviedb';
+
+ // Define model and columns
+ var tCompany = tc.createTEntity(t,'company','company_id');
+ var tCompanyId = tc.createTField(tCompany,'company_id');
+ var tCompanyName = tc.createTField(tCompany,'name');
+ var tCompanyDateEst = tc.createTField(tCompany,'date_est');
+ var tCompanyRemarks = tc.createTField(tCompany,'remarks');
+ var tCompanyCountryId = tc.createTField(tCompany,'country_id');
+
+ var tCountry = tc.createTEntity(t,'country','country_id');
+
+ var tCountryCompany = tc.createTEntity(tCountry,'company','company_id');
+ var tCountryCompanyId = tc.createTField(tCountryCompany,'company_id');
+ var tCountryCompanyName = tc.createTField(tCountryCompany,'name');
+ var tCountryCompanyDateEst = tc.createTField(tCountryCompany,'date_est');
+ var tCountryCompanyRemarks = tc.createTField(tCountryCompany,'remarks');
+ var tCountryCompanyCountryId = tc.createTField(tCountryCompany,'country_id');
+
+ var tCountryId = tc.createTField(tCountry,'country_id');
+ var tCountryCode = tc.createTField(tCountry,'code');
+ var tCountryName = tc.createTField(tCountry,'name');
+
+ var tDirector = tc.createTEntity(t,'director','director_id');
+ var tDirectorId = tc.createTField(tDirector,'director_id');
+ var tDirectorDateBorn = tc.createTField(tDirector,'date_born');
+ var tDirectorDateDied = tc.createTField(tDirector,'date_died');
+ var tDirectorFirstName = tc.createTField(tDirector,'first_name');
+ var tDirectorLastName = tc.createTField(tDirector,'last_name');
+
+ var tGenre = tc.createTEntity(t,'genre','genre_id');
+ var tGenreId = tc.createTField(tGenre,'genre_id');
+ var tGenreCode = tc.createTField(tGenre,'code');
+ var tGenreName = tc.createTField(tGenre,'name');
+
+ tCompany.tmeta.tmenu.ticon='fa fa-building';
+ //tCountryCompany.tmeta.tmenu.ticon='fa fa-building';
+ tCountry.tmeta.tmenu.ticon='fa fa-globe';
+ tDirector.tmeta.tmenu.ticon='fa fa-cogs';
+ tGenre.tmeta.tmenu.ticon='fa fa-star';
+}
diff --git a/example/lib/pg-pagila.js b/example/lib/pg-pagila.js
new file mode 100644
index 0000000..d927f84
--- /dev/null
+++ b/example/lib/pg-pagila.js
@@ -0,0 +1,31 @@
+var pgDB = require('pg');
+var pgDBNamed = require('node-postgres-named');
+
+module.exports = {
+ load: load,
+ setup: setup
+};
+
+function load(tcrud) {
+ // Create backend with id and uri
+ tcrud.backend.database.loadPostgres('pg/pagila','postgres://postgres:postgresql@localhost/pagila',pgDB,pgDBNamed);
+}
+
+function setup(tcrud,tcrudModel) {
+
+ // Create tcrud models
+ var tc = tcrud.config;
+ var t = tc.createTEntityNode(tcrudModel,'pagila');
+ t.tmeta.tmodel.tbackend = 'pg/pagila';
+
+ // Define model and columns
+ var tLanguage = tc.createTEntity(t,'language','language_id');
+ var tLanguageId = tc.createTField(tLanguage,'language_id');
+ var tLanguageName = tc.createTField(tLanguage,'name');
+ var tLanguageLastUpdate = tc.createTField(tLanguage,'last_update');
+
+ var tCountry = tc.createTEntity(t,'country','country_id');
+ var tCountryId = tc.createTField(tCountry,'country_id');
+ var tCountryCountry = tc.createTField(tCountry,'country');
+ var tCountryLastUpdate = tc.createTField(tCountry,'last_update');
+}
diff --git a/example/package.json b/example/package.json
new file mode 100644
index 0000000..78150f7
--- /dev/null
+++ b/example/package.json
@@ -0,0 +1,21 @@
+{
+ "name": "node-ff-tcrud-example",
+ "description": "node-ff-tcrud-example",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "start": "node example.js"
+ },
+ "dependencies": {
+ "cookie-parser": "^1.0.1",
+ "debug": "^2.2.0",
+ "express-winston": "0.3.1",
+ "static-favicon": "~1.0.0",
+ "winston": "~0.7.3",
+ "node-postgres-named": "^2.2.0",
+ "pg": "^4.4.0",
+ "mssql": "^2.1.6",
+ "mysql2": "^0.15.8",
+ "mongoose": "~3.8.13"
+ }
+}
diff --git a/example/www_views/js/page-bar.ejs b/example/www_views/js/page-bar.ejs
new file mode 100644
index 0000000..bf5c515
--- /dev/null
+++ b/example/www_views/js/page-bar.ejs
@@ -0,0 +1,10 @@
+
+pageRouteInit.push(function ($routeProvider, $locationProvider) {
+ $routeProvider.when('/ui/bar', {
+ templateUrl: '/ui/thtml/bar',
+ controller: PageFoo
+ });
+});
+
+function PageFoo($scope, $http) {
+}
diff --git a/example/www_views/js/page-foo.ejs b/example/www_views/js/page-foo.ejs
new file mode 100644
index 0000000..2538e69
--- /dev/null
+++ b/example/www_views/js/page-foo.ejs
@@ -0,0 +1,10 @@
+
+pageRouteInit.push(function ($routeProvider, $locationProvider) {
+ $routeProvider.when('/ui/foo', {
+ templateUrl: '/ui/thtml/foo',
+ controller: PageFoo
+ });
+});
+
+function PageFoo($scope, $http) {
+}
diff --git a/example/www_views/js/page-home.ejs b/example/www_views/js/page-home.ejs
new file mode 100644
index 0000000..5ae4d89
--- /dev/null
+++ b/example/www_views/js/page-home.ejs
@@ -0,0 +1,10 @@
+
+pageRouteInit.push(function ($routeProvider, $locationProvider) {
+ $routeProvider.when('/ui', {
+ templateUrl: '/ui/thtml/home',
+ controller: PageHome
+ });
+});
+
+function PageHome($scope, $http) {
+}
diff --git a/example/www_views/thtml/bar.ejs b/example/www_views/thtml/bar.ejs
new file mode 100644
index 0000000..edc34ff
--- /dev/null
+++ b/example/www_views/thtml/bar.ejs
@@ -0,0 +1,4 @@
+
+
Bar
+
Welcome to the bar.
+
diff --git a/example/www_views/thtml/foo.ejs b/example/www_views/thtml/foo.ejs
new file mode 100644
index 0000000..52c464f
--- /dev/null
+++ b/example/www_views/thtml/foo.ejs
@@ -0,0 +1,4 @@
+
+
Foo
+
Welcome to the foo.
+
diff --git a/example/www_views/thtml/home.ejs b/example/www_views/thtml/home.ejs
new file mode 100644
index 0000000..c6c6dcc
--- /dev/null
+++ b/example/www_views/thtml/home.ejs
@@ -0,0 +1,4 @@
+
+
Example UI Index
+
Welcome make yourself at home.
+
diff --git a/lib/backend/database.js b/lib/backend/database.js
new file mode 100644
index 0000000..8d5a98b
--- /dev/null
+++ b/lib/backend/database.js
@@ -0,0 +1,247 @@
+var tcrudSetup = require('./../tcrud-setup');
+var debug = require('debug')('ff:tcrud:backend:database');
+
+module.exports = {
+ loadModule: function(key,dbModule) {
+ tcrudSetup.pluginLoad(new DatabasePlugin(key,dbModule));
+ },
+ loadPostgres: function(key,dbUri,pgDB,pgDBNamed) {
+ tcrudSetup.pluginLoad(new DatabasePlugin(key,new PostgresModule(dbUri,pgDB,pgDBNamed)));
+ },
+ loadMysql2: function(key,pool) {
+ tcrudSetup.pluginLoad(new DatabasePlugin(key,new Mysql2Module(pool)));
+ },
+ loadMssql: function(key,driver,conn) {
+ tcrudSetup.pluginLoad(new DatabasePlugin(key,new MssqlModule(driver,conn)));
+ }
+}
+
+//------- PostgresModule Object
+
+function PostgresModule(dbUri,pgDB,pgDBNamed) {
+ this.dbUri = dbUri;
+ this.pgDB = pgDB;
+ this.pgDBNamed = pgDBNamed;
+}
+PostgresModule.prototype.query = function(sqlQuery,params, cb) {
+ var self = this;
+ self.pgDB.connect(self.dbUri, function(err, client, done) {
+ self.pgDBNamed.patch(client);
+ client.query(sqlQuery, params, function(err, result) {
+ done(); // release client from pool
+ cb(err,result);
+ });
+ });
+}
+
+function Mysql2Module(pool) {
+ this.pool = pool;
+}
+Mysql2Module.prototype.query = function(sqlQuery,params, cb) {
+ var self = this;
+ self.pool.getConnection(function(err, connection) {
+ connection.config.namedPlaceholders = true;
+ connection.execute(sqlQuery,params, function(err, rows) {
+ connection.release();
+ cb(err,rows);
+ });
+ });
+}
+
+function MssqlModule(driver,conn) {
+ this.driver = driver;
+ this.conn = conn;
+}
+MssqlModule.prototype.query = function(sqlQuery,params, cb) {
+ var self = this;
+ var request = new self.driver.Request(self.conn);
+ request.query(sqlQuery,params,function(err, rows) {
+ cb(err,rows);
+ });
+}
+
+//------- DatabasePlugin Object
+
+function DatabasePlugin(key,dbModule) {
+ this.key = key;
+ this.dbModule = dbModule;
+}
+
+DatabasePlugin.prototype.configPlugin = function (ctx) {
+ ctx.key='db#'+this.key;
+ ctx.description='Database api adapter for '+this.key;
+};
+
+DatabasePlugin.prototype.createBackend = function() {
+ return new DatabaseBackend(this);
+}
+
+DatabasePlugin.prototype.query = function(sqlQuery,params, cb) {
+ this.dbModule.query(sqlQuery,params,cb);
+}
+
+// -------- DatabaseBackend
+
+function findKeysView(tview) {
+ return tview.tkeys;
+}
+
+function findKeysModel(tview) {
+ return tview.tmeta.tmodel.tkeys;
+}
+
+function selectField(tview,crudType) {
+ var result = [];
+ tview[crudType].tfields.forEach(function (tfieldKey) {
+ var tfield = tview.tmeta.tfields[tfieldKey];
+ result.push(tfield.tid);
+ });
+ var resultSql = '';
+ for (var i = 0; i < result.length; i++) {
+ var key = result[i];
+ resultSql += key;
+ if (i < (result.length - 1)) {
+ resultSql += ',';
+ }
+ }
+ return resultSql;
+}
+
+function whereKeys(tview) {
+ var resultSql = '';
+ for (var i = 0; i < tview.tmeta.tmodel.tkeys.length; i++) {
+ var key = tview.tmeta.tmodel.tkeys[i];
+ //resultSql += key +' = $'+(i+1);
+ resultSql += key +' = $'+key;
+ if (i < (tview.tmeta.tmodel.tkeys.length - 1)) {
+ resultSlug += ' AND ';
+ }
+ }
+ return resultSql;
+}
+
+function updateValues(tview,data) {
+ var resultSql = 'SET ';
+ var dataKeys = Object.keys(data);
+ for (var i = 0; i < dataKeys.length; i++) {
+ var key = dataKeys[i];
+
+ var skip = false;
+ for (var ii = 0; ii < tview.tmeta.tmodel.tkeys.length; ii++) {
+ var keyPK = tview.tmeta.tmodel.tkeys[ii];
+ if (key === keyPK) {
+ skip = true;
+ break;
+ }
+ }
+ if (skip === true) {
+ continue;
+ }
+ resultSql += key +' = $'+key;
+ if (i < (dataKeys.length - 1)) {
+ resultSql += ',';
+ }
+ }
+ return resultSql;
+}
+
+function DatabaseBackend(plugin) {
+ this.plugin = plugin;
+}
+
+DatabaseBackend.prototype.getKey = function() {
+ return this.plugin.key;
+}
+
+DatabaseBackend.prototype.findAll = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'SELECT '+selectField(tview,crudType)+' FROM '+tview.tmeta.tmodel.tid+'';
+ debug('findAll %s',querySql);
+ var query = self.plugin.query(querySql,[],function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows);
+ }
+ });
+ };
+}
+
+DatabaseBackend.prototype.createNew = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+
+ };
+}
+
+DatabaseBackend.prototype.findOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'SELECT '+selectField(tview,crudType)+' FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview)+'';
+ debug('findOne param %s',JSON.stringify(dataParam));
+ debug('findOne sql %s',querySql);
+ var query = self.plugin.query(querySql, dataParam, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
+DatabaseBackend.prototype.updateOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'UPDATE '+tview.tmeta.tmodel.tid+' '+updateValues(tview,data)+' WHERE '+whereKeys(tview);
+ debug('updateOne param %s',JSON.stringify(dataParam));
+ debug('updateOne data %s',JSON.stringify(data));
+ debug('updateOne sql %s',querySql);
+ var query = self.plugin.query(querySql, data, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
+DatabaseBackend.prototype.deleteOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'DELETE FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview);
+ debug('deleteOne param %s',JSON.stringify(dataParam));
+ debug('deleteOne sql %s',querySql);
+ var query = self.plugin.query(querySql, dataParam, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
+DatabaseBackend.prototype.findAllCount = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'SELECT count(*) FROM '+tview.tmeta.tmodel.tid; //+' WHERE '+whereKeys(tview);
+ debug('findAllCount param %s',JSON.stringify(dataParam));
+ debug('findAllCount sql %s',querySql);
+ var query = self.plugin.query(querySql, dataParam, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
diff --git a/lib/backend/ldapjs.js b/lib/backend/ldapjs.js
new file mode 100644
index 0000000..9cab946
--- /dev/null
+++ b/lib/backend/ldapjs.js
@@ -0,0 +1,150 @@
+var config = require('./../tcrud-config');
+var debug = require('debug')('ff:tcrud:ldapjs');
+
+module.exports = {
+ registrate: function(key,client) {
+ config.registratePlugin(new LdapJSPlugin(key,client));
+ }
+}
+
+// ------- LdapJSPlugin Object
+
+function LdapJSPlugin(key,client) {
+ this.key = key;
+ this.client = client;
+}
+
+LdapJSPlugin.prototype.configPlugin = function (ctx) {
+ ctx.key='ldapjs#'+this.key;
+ ctx.description='Adds ldap backend support.';
+};
+
+LdapJSPlugin.prototype.createBackend = function() {
+ return new LdapJSBackend(this);
+}
+
+// -------- LdapJSBackend Object
+
+function LdapJSBackend(plugin) {
+ this.plugin = plugin;
+}
+
+LdapJSBackend.prototype.getKey = function() {
+ return this.plugin.key;
+}
+
+LdapJSBackend.prototype.findAll = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var opts = {
+ filter: '(&(l=Seattle)(email=*@foo.com))',
+ scope: 'sub'
+ };
+ debug('findAll %s',opts);
+ self.plugin.client.search('o=example', opts, function(err, res) {
+ if (err) {
+ debug(err);
+ cb(err);
+ return;
+ }
+ //var querySql = 'SELECT '+selectField(tview,crudType)+' FROM '+tview.tmeta.tmodel.tid+'';
+ var result = [];
+ res.on('searchEntry', function(entry) {
+ result.push(entry);
+ });
+ res.on('end', function(result) {
+ cb(err,result);
+ });
+ });
+ };
+}
+
+LdapJSBackend.prototype.createNew = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var entry = {
+ cn: 'foo',
+ sn: 'bar',
+ email: ['foo@bar.com', 'foo1@bar.com'],
+ objectclass: 'fooPerson'
+ };
+ self.plugin.client.add('cn=foo, o=example', entry, function(err) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows);
+ }
+ });
+ };
+}
+
+LdapJSBackend.prototype.findOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'SELECT '+selectField(tview,crudType)+' FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview)+'';
+ debug('findOne param %s',JSON.stringify(dataParam));
+ debug('findOne sql %s',querySql);
+ var query = self.db.query(querySql, dataParam, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
+LdapJSBackend.prototype.updateOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'UPDATE '+tview.tmeta.tmodel.tid+' '+updateValues(tview,data)+' WHERE '+whereKeys(tview);
+ debug('updateOne param %s',JSON.stringify(dataParam));
+ debug('updateOne data %s',JSON.stringify(data));
+ debug('updateOne sql %s',querySql);
+ var query = self.db.query(querySql, data, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
+LdapJSBackend.prototype.deleteOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'DELETE FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview);
+ debug('deleteOne param %s',JSON.stringify(dataParam));
+ debug('deleteOne sql %s',querySql);
+ var query = self.db.query(querySql, dataParam, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
+LdapJSBackend.prototype.findAllCount = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'SELECT count(*) FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview);
+ debug('findAllCount param %s',JSON.stringify(dataParam));
+ debug('findAllCount sql %s',querySql);
+ var query = self.db.query(querySql, dataParam, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
diff --git a/lib/backend/mongo.js b/lib/backend/mongo.js
new file mode 100644
index 0000000..ce6d751
--- /dev/null
+++ b/lib/backend/mongo.js
@@ -0,0 +1,127 @@
+var config = require('./../tcrud-config');
+var debug = require('debug')('ff:tcrud:mongo');
+
+module.exports = {
+ registrate: function(key,server) {
+ config.registratePlugin(new MongoPlugin(key,server));
+ }
+}
+
+// ------- MongoPlugin Object
+
+function MongoPlugin(key,server) {
+ this.key = key;
+ this.server = server;
+}
+
+MongoPlugin.prototype.configPlugin = function (ctx) {
+ ctx.key='mongo#'+this.key;
+ ctx.description='Adds mongo backend support.';
+};
+
+MongoPlugin.prototype.createBackend = function() {
+ return new MongoBackend(this);
+}
+
+// -------- MongoBackend
+
+function MongoBackend(plugin) {
+ this.plugin = plugin;
+}
+
+MongoBackend.prototype.getKey = function() {
+ return this.plugin.key;
+}
+
+MongoBackend.prototype.findAll = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'SELECT '+selectField(tview,crudType)+' FROM '+tview.tmeta.tmodel.tid+'';
+ debug('findAll %s',querySql);
+ var query = self.db.query(querySql,[],function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows);
+ }
+ });
+ };
+}
+
+MongoBackend.prototype.createNew = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+
+ };
+}
+
+MongoBackend.prototype.findOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'SELECT '+selectField(tview,crudType)+' FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview)+'';
+ debug('findOne param %s',JSON.stringify(dataParam));
+ debug('findOne sql %s',querySql);
+ var query = self.db.query(querySql, dataParam, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
+MongoBackend.prototype.updateOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'UPDATE '+tview.tmeta.tmodel.tid+' '+updateValues(tview,data)+' WHERE '+whereKeys(tview);
+ debug('updateOne param %s',JSON.stringify(dataParam));
+ debug('updateOne data %s',JSON.stringify(data));
+ debug('updateOne sql %s',querySql);
+ var query = self.db.query(querySql, data, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
+MongoBackend.prototype.deleteOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'DELETE FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview);
+ debug('deleteOne param %s',JSON.stringify(dataParam));
+ debug('deleteOne sql %s',querySql);
+ var query = self.db.query(querySql, dataParam, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
+MongoBackend.prototype.findAllCount = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ var querySql = 'SELECT count(*) FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview);
+ debug('findAllCount param %s',JSON.stringify(dataParam));
+ debug('findAllCount sql %s',querySql);
+ var query = self.db.query(querySql, dataParam, function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows[0]);
+ }
+ });
+ };
+}
+
diff --git a/lib/backend/mongoose.js b/lib/backend/mongoose.js
new file mode 100644
index 0000000..3d34c25
--- /dev/null
+++ b/lib/backend/mongoose.js
@@ -0,0 +1,271 @@
+var config = require('./../tcrud-config');
+var debug = require('debug')('ff:tcrud:mongoose');
+var clone = require('clone');
+var validate = require('validate.io');
+
+// ------- MongoosePlugin Object
+
+function MongoosePlugin(key,conn) {
+ this.key = key;
+ this.conn = conn;
+}
+
+MongoosePlugin.prototype.configPlugin = function (ctx) {
+ ctx.key='mongoose#'+this.key;
+ ctx.description='Adds ldap backend support.';
+};
+
+MongoosePlugin.prototype.createBackend = function() {
+ return new MongooseBackend(this);
+}
+
+//------- MongooseBackend Object
+
+function MongooseBackend(plugin) {
+ this.plugin = plugin;
+}
+
+MongooseBackend.prototype.getKey = function() {
+ return this.plugin.key;
+}
+
+MongooseBackend.prototype.findAll = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ debug('find all..'+tview.tmeta.tmodel.tid);
+ var model = self.plugin.conn.model(tview.tmeta.tmodel.tid);
+ model.find({},function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows);
+ }
+ });
+// cb();
+// var querySql = 'SELECT '+selectField(tview,crudType)+' FROM '+tview.tmeta.tmodel.tid+'';
+// debug('findAll %s',querySql);
+// var query = self.db.query(querySql,[],
+ };
+}
+
+MongooseBackend.prototype.createNew = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+
+ };
+}
+
+MongooseBackend.prototype.findOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ debug('findOne param %s',JSON.stringify(dataParam));
+ //debug('findOne sql %s',querySql);
+ var model = self.plugin.conn.model(tview.tmeta.tmodel.tid);
+ model.findById(dataParam,function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows);
+ }
+ });
+
+// var querySql = 'SELECT '+selectField(tview,crudType)+' FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview)+'';
+//
+ };
+}
+
+MongooseBackend.prototype.updateOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+ debug('updateOne param %s',JSON.stringify(dataParam));
+ debug('updateOne data %s',JSON.stringify(data));
+ var model = self.plugin.conn.model(tview.tmeta.tmodel.tid);
+ model.save(data,function (err, result) {
+ if (err) {
+ debug(err);
+ cb(err);
+ } else {
+ cb(err,result.rows);
+ }
+ });
+
+// var querySql = 'UPDATE '+tview.tmeta.tmodel.tid+' '+updateValues(tview,data)+' WHERE '+whereKeys(tview);
+//
+// debug('updateOne sql %s',querySql);
+// var query = self.db.query(querySql, data, function (err, result) {
+// if (err) {
+// debug(err);
+// cb(err);
+// } else {
+// cb(err,result.rows[0]);
+// }
+// });
+ };
+}
+
+MongooseBackend.prototype.deleteOne = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+// var querySql = 'DELETE FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview);
+// debug('deleteOne param %s',JSON.stringify(dataParam));
+// debug('deleteOne sql %s',querySql);
+// var query = self.db.query(querySql, dataParam, function (err, result) {
+// if (err) {
+// debug(err);
+// cb(err);
+// } else {
+// cb(err,result.rows[0]);
+// }
+// });
+ };
+}
+
+MongooseBackend.prototype.findAllCount = function(tview,crudType) {
+ var self = this;
+ return function(data, dataParam, cb) {
+// var querySql = 'SELECT count(*) FROM '+tview.tmeta.tmodel.tid+' WHERE '+whereKeys(tview);
+// debug('findAllCount param %s',JSON.stringify(dataParam));
+// debug('findAllCount sql %s',querySql);
+// var query = self.db.query(querySql, dataParam, function (err, result) {
+// if (err) {
+// debug(err);
+// cb(err);
+// } else {
+// cb(err,result.rows[0]);
+// }
+// });
+ };
+}
+
+
+
+
+
+
+function autoFieldType(fieldMeta,fieldType) {
+ if (!fieldMeta) {
+ throw new Error('no fieldMeta');
+ }
+ if (fieldType && fieldType.length != 0) {
+ return fieldType;
+ }
+ if (fieldMeta.type == Date) {
+ return 'date';
+ }
+ return 'text';
+}
+
+buildFields = function(modelMeta) {
+ if (!modelMeta) {
+ throw new Error('no modelMeta');
+ }
+ var tfields = {};
+ var keys = Object.keys(modelMeta);
+ for (i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var value = modelMeta[key];
+ var tfield = null;
+ if (key && value && value.tfield) {
+ debug('tfield model cloned');
+ tfield = clone(value.tfield);
+ tfield.tid = key;
+ tfield.tname = tfield.tname;
+ tfield.type = autoFieldType(value,tfield.ttype);
+ } else if (key && value) {
+ debug('tfield model auto created');
+ tfield = config.createTField(key);
+ tfield.type = autoFieldType(value);
+ }
+ if (tfield.tvalidate && tfield.tvalidate.io) {
+ debug('tfield validate rule: '+tfield.tvalidate.io);
+ }
+ tfields[tfield.tid] = tfield;
+ }
+ return tfields;
+}
+
+function ss(valueRule) {
+ return function (value, response) {
+ response(validate(valueRule,value));
+ };
+}
+
+createModelValidators = function (modelSchema,modelFields) {
+ if (!modelSchema) {
+ throw new Error('no modelSchema');
+ }
+ if (!modelFields) {
+ throw new Error('no modelFields');
+ }
+ var keys = Object.keys(modelFields);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var tfield = modelFields[key];
+ if (!tfield.tvalidate) {
+ continue;
+ }
+ if (!tfield.tvalidate.io) {
+ continue;
+ }
+ modelSchema.path(tfield.tid).validate(ss(tfield.tvalidate.io), '{PATH} validation failed: '+tfield.tvalidate.io);
+ }
+}
+
+buildStatics = function(modelFields,modelStatics) {
+ if (!modelFields) {
+ throw new Error('no modelFields');
+ }
+ if (!modelStatics) {
+ modelStatics = {};
+ }
+ modelStatics['ff_tcrud_fields'] = modelFields;
+ return modelStatics;
+}
+
+buildTEntityModel = function(mongoose,tcrudParent,name) {
+ var model = mongoose.model(name);
+ var tcrud = config.createTEntity(tcrudParent,name, '_id');
+ var tfields = model['ff_tcrud_fields'];
+ if (tfields) {
+ tcrud.tmeta.tfields = tfields;
+ }
+ tcrud.tmodel = name;
+
+ return tcrud;
+}
+
+buildTEntityModels = function(conn,tcrudParent) {
+ var result = [];
+ var modelNames = conn.modelNames();
+ for (var i = 0; i < modelNames.length; i++) {
+ result.push(buildTEntityModel(conn,tcrudParent,modelNames[i]))
+ }
+ return result;
+}
+
+
+// ----- wrappers
+
+buildStaticsModel = function(modelMeta,modelStatics) {
+ return buildStatics(buildFields(modelMeta),modelStatics);
+}
+
+buildStaticsModelValidated = function(modelMeta,modelSchema,modelStatics) {
+ var modelFields = buildFields(modelMeta);
+ createModelValidators(modelSchema,modelFields);
+ return buildStatics(modelFields,modelStatics);
+}
+
+
+
+module.exports = {
+ buildTEntityModels: buildTEntityModels,
+ buildStaticsModelValidated: buildStaticsModelValidated,
+ buildFields: buildFields,
+ registrate: function(key,conn) {
+ config.registratePlugin(new MongoosePlugin(key,conn));
+ }
+}
+
diff --git a/lib/build-server-api-tentity.js b/lib/build-server-api-tentity.js
new file mode 100644
index 0000000..d0eacb2
--- /dev/null
+++ b/lib/build-server-api-tentity.js
@@ -0,0 +1,261 @@
+var debug = require('debug')('ff:tcrud:build:server:api:tentity');
+var fs = require('fs');
+var ejs = require('ejs');
+var ncrud = require('node-crud');
+var configTCrud = require('./tcrud-config');
+var configRegistry = require('./config-registry');
+var buildServerApiUrl = require('./build-server-api-url');
+var buildServerExpress = require('./build-server-express');
+var _ = require('underscore');
+
+var mod = (function () {
+
+ var renderTemplateDataRead = function(tview, exportType, contentType) {
+ return function(data, query, cb) {
+ var res = this.response;
+ res.set('Content-Type', contentType);
+
+ //var templateReadHeader = fs.readFileSync('www_views/node-ff-tcrud/'+exportType+'/read-header.ejs', 'utf8');
+ var templateReadRecord = fs.readFileSync('./../lib/www_views/node-ff-tcrud/'+exportType+'/read-record.ejs', 'utf8');
+ //var templateReadFooter = fs.readFileSync('www_views/node-ff-tcrud/'+exportType+'/read-footer.ejs', 'utf8');
+
+ //var result = ejs.render(templateHeader,{
+ // tview: tview,
+ //});
+ for (var i = 0; i < data.length; i++) {
+ var row = data[i];
+ result += ejs.render(templateRecord,{
+ tview: tview,
+ record: row,
+ });
+ }
+ //result +=ejs.render(templateFooter,{
+ // tview: tview,
+ //});
+ res.write(result);
+ res.end();
+ this.close(); // end chain (cb set headers)
+ }
+ };
+
+ var renderTemplateDataList = function(tview, exportType, contentType) {
+ return function(data, query, cb) {
+ var res = this.response;
+ //res.set('Content-Type', contentType);
+
+ var templateHeader = fs.readFileSync('./../lib/www_views/node-ff-tcrud/'+exportType+'/list-header.ejs', 'utf8');
+ var templateRecord = fs.readFileSync('./../lib/www_views/node-ff-tcrud/'+exportType+'/list-record.ejs', 'utf8');
+ var templateFooter = fs.readFileSync('./../lib/www_views/node-ff-tcrud/'+exportType+'/list-footer.ejs', 'utf8');
+
+ var result = ejs.render(templateHeader,{
+ tview: tview,
+ });
+ for (var i = 0; i < data.length; i++) {
+ var row = data[i];
+ result += ejs.render(templateRecord,{
+ tview: tview,
+ record: row,
+ });
+ }
+ result +=ejs.render(templateFooter,{
+ tview: tview,
+ });
+ res.write(result);
+ res.end();
+ this.close(); // end chain (cb set headers)
+ };
+ };
+
+ var filterQueryList = function (model) {
+ return mcrud.parseQuery()
+ .defaults({ limit: '10' })
+ .overrides({})
+ .maxes({ limit: 1000 });
+ //.removes()
+ };
+
+ var filterDataUpdate = function (model) {
+ return mcrud.parseData()
+ overrides({});
+ //.removes('auth')
+ //.overrides({ updated: Date.now })
+ //.defaults({ 'info.gender': 'M' }))
+ };
+
+ function allowCrudType(tview,tcrudType) {
+ return tview[tcrudType].tenable; // todo add roles
+ };
+
+ var createMethodObject = function (entityObject,httpMethod) {
+ if ('GET' === httpMethod) {
+ return entityObject.Read();
+ } else if ('POST' === httpMethod) {
+ return entityObject.Create();
+ } else if ('PUT' === httpMethod) {
+ return entityObject.Update();
+ } else if ('DELETE' === httpMethod) {
+ return entityObject.Delete();
+ } else {
+ throw 'unknown httpMethod: '+httpMethod;
+ }
+ };
+
+ var createMethodExport = function (ctx,crudType,backendPipe,keySlug,exportMethod,exportMethodPre,exportMethodUse) {
+ var plugin = ctx.tplugin;
+ if (plugin[exportMethod] && allowCrudType(ctx.tview,crudType)) {
+ var apiUrl = buildServerApiUrl.createSlugApiTEntity(plugin.tmeta.key,ctx.tview,crudType) + keySlug;
+ var entityObject = ncrud.entity(apiUrl);
+ for (var ii = 0; ii < ctx.tview[crudType].tmethods.length; ii++) {
+ var httpMethod = ctx.tview[crudType].tmethods[ii];
+ var methodObject = createMethodObject(entityObject,httpMethod);
+ debug(plugin.tmeta.key+'.'+httpMethod+' for: '+crudType);
+ if (plugin[exportMethodUse]) {
+ methodObject.use(plugin[exportMethodUse](ctx));
+ }
+ //methodObject.pipe(filterQueryList(model))
+ //methodObject.pipe(filterDateUpdate(model))
+ if (plugin[exportMethodPre]) {
+ methodObject.pipe(plugin[exportMethodPre](ctx));
+ }
+ methodObject.pipe(backendPipe);
+ methodObject.pipe(plugin[exportMethod](ctx));
+
+ methodObject.on('close', function(event) {
+ debug('event close: '+event)
+ });
+
+ ctx.tplugin['troutes'].push({
+ urlPath: ctx.tview.tmeta.tserver.tslugs.tbase + '/' + apiUrl,
+ httpMethod: httpMethod
+ });
+ }
+ }
+ };
+
+ var registrateMenu2 = function(menu,key) {
+ if (menu['items'] === undefined) {
+ menu['items'] = [];
+ }
+ configRegistry.getMasterConfig().rootTMenu[key] = menu; // TODO; move to reg
+ };
+
+ var buildCrud = function (server,tcrud) {
+
+ if (!tcrud.tenable) {
+ debug('skip tentity by tenable: %s',tcrud.tid);
+ return;
+ }
+ var tview = configTCrud.createTView(tcrud /*, fetchCrudRoles()*/);
+
+ if (tview.tmeta.tmenu.tenable && tview.tmeta.tmenu.tkey !== null && tview.tmeta.tmenu.titem === false) {
+ registrateMenu2({
+ name: tview.tmeta.tmenu.tname,
+ icon: tview.tmeta.tmenu.ticon
+ },tview.tmeta.tmenu.tkey);
+ }
+
+ if (!tview.tmeta.tmodel.tbackend) {
+ debug('skip tentity no backend: %s',tcrud.tid);
+ return;
+ }
+ if (!tview.tmeta.tmodel.tid) {
+ debug('skip tentity no model: %s',tcrud.tid);
+ return;
+ }
+ var backend = configTCrud.getBackend(tview.tmeta.tmodel.tbackend);
+ if (!backend) {
+ debug('skip tentity illegal backend: %s',tcrud.tid);
+ return;
+ }
+ var keySlug = '/'+tview.tmeta.tmodel.tkey; // TODO: remove me
+ for (var i = 0; i < configRegistry.getMasterConfig().plugins.length; i++) {
+ var plugin = configRegistry.getMasterConfig().plugins[i];
+ var pluginKey = plugin.tmeta.key;
+ if (!_.contains(tview.tmeta.tserver.tformats,pluginKey)) {
+ continue;
+ }
+
+ var ctx = {
+ tplugin: plugin,
+ tview: tview,
+ createSlugApiTEntityBasePlugin: function(key,postfix) {
+ return buildServerApiUrl.createSlugApiTEntityBase(key,ctx.tview,postfix);
+ },
+ createSlugApiTEntityBase: function(postfix) {
+ return buildServerApiUrl.createSlugApiTEntityBase(pluginKey,ctx.tview,postfix); //createSlugApiTEntity: buildServerUrl.createSlugApiTEntity,
+ },
+
+ renderTemplateDataList: function(contentType) {
+ return renderTemplateDataList(ctx.tview,pluginKey,contentType);
+ },
+ renderTemplateDataRead: function(contentType) {
+ return renderTemplateDataRead(ctx.tview,pluginKey,contentType);
+ },
+
+ registrateMenu: function(menu,key) {
+ if (menu['items'] === undefined) {
+ menu['items'] = [];
+ }
+ configRegistry.getMasterConfig().rootTMenu[key] = menu;
+ },
+ registrateMenuItem: function(menuItem,key) {
+ if (configRegistry.getMasterConfig().rootTMenu[key] === undefined) {
+ return; // sub tentities
+ }
+ configRegistry.getMasterConfig().rootTMenu[key].items.push(menuItem);
+ },
+
+ registrateClientJSResource: function(clientResource) {
+ configRegistry.registrateClientResource(clientResource,'js');
+ },
+ registrateClientCSSResource: function(clientResource) {
+ configRegistry.registrateClientResource(clientResource,'css'); // +data or rm others
+ },
+ renderFunctionJSON: buildServerExpress.renderFunctionJSON,
+ server: server
+ }
+ if (plugin.configApi) {
+ debug(pluginKey+'.configApi(tview='+tview.tid+',backend='+tview.tmeta.tmodel.tbackend+')');
+ plugin.configApi(ctx);
+ }
+ createMethodExport(ctx,'tlist', backend.findAll(tview, 'tlist'), '', 'configApiTListExport', 'configApiTListExportPre', 'configApiTListExportUse');
+ createMethodExport(ctx,'tcreate', backend.createNew(tview, 'tcreate'), '', 'configApiTCreateExport', 'configApiTCreateExportPre', 'configApiTCreateExportUse');
+ createMethodExport(ctx,'tread', backend.findOne(tview, 'tread'), keySlug, 'configApiTReadExport', 'configApiTReadExportPre', 'configApiTReadExportUse');
+ createMethodExport(ctx,'tedit', backend.updateOne(tview, 'tedit'), keySlug, 'configApiTEditExport', 'configApiTEditExportPre', 'configApiTEditExportUse');
+ createMethodExport(ctx,'tdelete', backend.deleteOne(tview, 'tdelete'), keySlug, 'configApiTDeleteExport', 'configApiTDeleteExportPre', 'configApiTDeleteExportUse');
+ createMethodExport(ctx,'tcount', backend.findAllCount(tview,'tcount'), '', 'configApiTCountExport', 'configApiTCountExportPre', 'configApiTCountExportUse');
+
+ if (allowCrudType(tview,'tverify')) {
+ debug('todo verift');
+ //ncrud.entity(buildView.createBaseApiUri(tview,'json','tverify') + '/:_id').Update()
+ // .pipe(mcrud.findOne(model));
+ }
+ }
+ };
+
+ function walkTEntityTree(server,tentity) {
+ if (tentity.tparent) { // TODO: move skip root node
+ buildCrud(server,tentity);
+ }
+ for (var i = 0; i < tentity.tchilds.length; i++) {
+ walkTEntityTree(server,tentity.tchilds[i]);
+ }
+ };
+
+ return function BuildServerApiTEntity() {
+
+ this.build = function(server) {
+ debug('buildServerApiTEntity');
+ configRegistry.assertPhaseServer();
+ var troot = configTCrud.getRootTEntity();
+ walkTEntityTree(server,troot);
+ ncrud.launch(server,{
+ base: troot.tmeta.tserver.tslugs.tbase,
+ cors: false,
+ timeoute: troot.tmeta.tserver.tapi.timeoute
+ });
+ };
+ };
+})();
+
+module.exports = new mod();
diff --git a/lib/build-server-api-url.js b/lib/build-server-api-url.js
new file mode 100644
index 0000000..fa2a2da
--- /dev/null
+++ b/lib/build-server-api-url.js
@@ -0,0 +1,59 @@
+var debug = require('debug')('ff:tcrud:build:server:api:url');
+var configTCrud = require('./tcrud-config');
+
+var mod = (function () {
+
+ var createSlugApi = function(pluginId,postfix,slugType) {
+ var troot = configTCrud.getRootTEntity();
+ var pluginSlug = pluginId;
+ if (troot.tmeta.tplugin[pluginId] !== undefined && troot.tmeta.tplugin[pluginId].tslug !== undefined) {
+ pluginSlug = troot.tmeta.tplugin[pluginId].tslug;
+ }
+ var result = troot.tmeta.tserver.tslugs.tbase+
+ '/'+troot.tmeta.tserver.tslugs[slugType]+
+ '/'+pluginSlug;
+ if (postfix) {
+ result +='/'+postfix;
+ }
+ debug('createSlugApi plugin: '+pluginId+' result: '+result);
+ return result;
+ };
+
+ var createSlugApiTEntityAction = function(pluginId,tview, action) {
+ var uriEntity = tview.tmeta.tserver.tslugs.tentity;
+ var uriView = tview.tslug;
+ var uriTech = tview.tmeta.tplugin[pluginId].tslug;
+ var uriAction = '';
+ if (action) {
+ uriAction = '/' + tview[action].tplugin[pluginId].tslug;
+ }
+ //debug('createBaseApiUri uriTech: '+uriTech+' uriView: '+uriView+' uriAction: '+uriAction);
+ if (tview.tmeta.tserver.tpopfix) {
+ return uriEntity + '/' + uriTech + '/' + uriView + uriAction;
+ } else {
+ return uriEntity + '/' +uriView + '/' + uriTech + uriAction;
+ }
+ };
+
+ return function BuildServerApiUrl() {
+
+ this.createSlugApiServerBase = function(pluginId,postfix) {
+ return createSlugApi(pluginId,postfix,'tserver');
+ };
+
+ this.createSlugApiPluginBase = function(pluginId,postfix) {
+ return createSlugApi(pluginId,postfix,'tplugin');
+ };
+
+ this.createSlugApiTEntityBase = function(pluginId,tview,action) {
+ var troot = configTCrud.getRootTEntity();
+ return troot.tmeta.tserver.tslugs.tbase+'/'+createSlugApiTEntityAction(pluginId,tview,action);
+ };
+
+ this.createSlugApiTEntity = function(pluginId,tview,action) {
+ return createSlugApiTEntityAction(pluginId,tview,action);
+ };
+ };
+})();
+
+module.exports = new mod();
diff --git a/lib/build-server-api.js b/lib/build-server-api.js
new file mode 100644
index 0000000..f436387
--- /dev/null
+++ b/lib/build-server-api.js
@@ -0,0 +1,253 @@
+var debug = require('debug')('ff:tcrud:build:server:api');
+var clone = require('clone');
+var Fontmin = require('fontmin');
+var fs = require('fs');
+var configTCrud = require('./tcrud-config');
+var configRegistry = require('./config-registry');
+var buildServerApiUrl = require('./build-server-api-url');
+var buildServerExpress = require('./build-server-express');
+
+
+var mod = (function () {
+
+ var renderTemplate = function(pluginSlug,templateFile, contentType) {
+ var renderFile = 'node-ff-tcrud/'+pluginSlug+'/'+templateFile;
+ var troot = configTCrud.getRootTEntity();
+ return function (req, res, next) {
+ res.set('Content-Type', contentType);
+ res.render(renderFile,{
+ troot: troot,
+ query: req.query
+ });
+ };
+ }
+
+ var hostTemplate = function(ctx, templateFile, contentType, skipReg) {
+ var pluginKey = ctx.tplugin.tmeta.key;
+ var apiPath = buildServerApiUrl.createSlugApiPluginBase(pluginKey,templateFile);
+ debug('hostTemplate apiPath: '+apiPath+' templateFile: '+templateFile+' content-Type: '+contentType);
+ ctx.server.get(apiPath, renderTemplate(pluginKey,templateFile, contentType)); // TODO: check sluging
+ if (skipReg !== undefined) {
+ return;
+ }
+
+ if (contentType.indexOf('css') !== -1) {
+ configRegistry.registrateClientResource(apiPath,'css');
+ }
+ if (contentType.indexOf('javascript') !== -1) {
+ configRegistry.registrateClientResource(apiPath,'js');
+ }
+ // TODO: no (t)html resources for caching ?
+ }
+
+ var filterContent = function(data,regexData) {
+ if (regexData === undefined) {
+ return data;
+ }
+ data = ''+data;
+ var regexList = Object.keys(regexData);
+ for (var i = 0; i < regexList.length; i++) {
+ var regex = new RegExp(regexList[i],'gm');
+ var regexReplace = regexData[regexList[i]];
+ data = data.replace(regex,regexReplace);
+ }
+ return data;
+ };
+
+ var hostFile = function (ctx,opt,basePath,optContentTypeHint) {
+
+ if (opt === undefined) {
+ throw new Error('No options given');
+ }
+ if (opt.file === undefined) {
+ throw new Error('No file option given');
+ }
+
+ if (optContentTypeHint !== undefined) {
+ if (optContentTypeHint === 'js') {
+ opt.contentType = 'text/javascript';
+ } else {
+ opt.contentType = 'text/css';
+ }
+ }
+
+ if (opt.fileCharset === undefined) {
+ opt.fileCharset = 'utf8';
+ }
+ if (opt.contentType === undefined) {
+ opt.contentType = 'text/html';
+ }
+ if (opt.registrate === undefined) {
+ opt.registrate = true;
+ }
+
+ var file = basePath;
+ if (opt.path !== undefined) {
+ file = file + opt.path;
+ }
+ file = file + '/' + opt.file;
+ debug('read host file: '+file);
+ var fileContent = fs.readFileSync(file, opt.fileCharset);
+ if (opt.filterFn !== undefined) {
+ fileContent = opt.filterFn(fileContent);
+ }
+ var serverContent = filterContent(fileContent, opt.filterRegex);
+ var serverContentType = opt.contentType;
+
+ var apiPath = buildServerApiUrl.createSlugApiPluginBase(ctx.tplugin.tmeta.key,opt.file);
+ ctx.server.get(apiPath, function(req, res) {
+ res.set('Content-Type', serverContentType);
+ res.send(serverContent);
+ });
+ if (opt.registrate && opt.contentType === 'text/javascript') {
+ configRegistry.registrateClientResource(apiPath,'js');
+ }
+ if (opt.registrate && opt.contentType === 'text/css') {
+ configRegistry.registrateClientResource(apiPath,'css');
+ }
+ };
+
+ // FIXME: a bit hacky and not compleet or ready...
+ var hostFileCssFont = function(ctx,opt,basePath) {
+
+ if (opt === undefined) {
+ throw new Error('No options given');
+ }
+ if (opt.file === undefined) {
+ throw new Error('No file option given');
+ }
+
+ var filename = basePath;
+ if (opt.path !== undefined) {
+ filename += opt.path;
+ }
+ filename += opt.file;
+
+ var fontmin = new Fontmin()
+ fontmin.src(fs.readFileSync(filename));
+ if (filename.toLowerCase().indexOf('.otf') > 0) {
+ fontmin.use(Fontmin.otf2ttf());
+ }
+ //.use(Fontmin.otf2ttf()).use(Fontmin.ttf2woff()); // slow...
+ fontmin.run(function (err, files, stream) {
+
+ var fontBuffer = files[0].contents;
+ debug('fond build done..'+files.length);
+ debug('fond build done..'+Object.keys(files[0]));
+ var fontStr = fontBuffer.toString('base64');
+ var fontWeight = '';
+ if (opt.fontWeight !== undefined) {
+ fontWeight = '\tfont-weight: '+opt.fontWeight+';\n';
+ }
+ var result = '\n'+
+ '@font-face {\n'+
+ ' font-family: "'+opt.fontFamily+'";\n'+
+ ' src: url("data:font/ttf;charset=utf-8;base64,'+fontStr+'") format("truetype");\n'+
+ fontWeight+
+ '}\n';
+ ///' src: url("data:font/ttf;charset=utf-8;base64,'+fontStr+'") format("truetype");\n'+
+ //cb(result); // ' font-style: normal;\n'+ //font/opentype
+ //' src: url("data:application/x-font-woff;charset=utf-8;base64,'+fontData+'") format("woff");\n'+
+ ctx.server.get(apiPath, function(req, res) {
+ res.set('Content-Type', 'text/css');
+ res.write(result);
+ res.end();
+ });
+
+ });
+
+ // TODO: async build path ?
+ var apiPath = ctx.createSlugApiPluginBase(opt.file);
+ ctx.registrateClientCSSDataResource(apiPath);
+ }
+
+ var buildCrudApi = function(server) {
+ var troot = configTCrud.getRootTEntity();
+ var ctx = {
+ server: server,
+ troot: troot,
+
+ registrateMenu: function(menu,key) {
+ if (menu['items'] === undefined) {
+ menu['items'] = [];
+ }
+ configRegistry.getMasterConfig().rootTMenu[key] = menu;
+ },
+ registrateMenuItem: function(menuItem,key) {
+ configRegistry.getMasterConfig().rootTMenu[key].items.push(menuItem);
+ },
+
+ registrateClientJSResource: function(clientResource) {
+ return configRegistry.registrateClientResource(clientResource,'js');
+ },
+ registrateClientCSSResource: function(clientResource) {
+ return configRegistry.registrateClientResource(clientResource,'css');
+ },
+ registrateClientCSSDataResource: function(clientResource) {
+ return configRegistry.registrateClientResource(clientResource,'dss');
+ },
+
+ createSlugApiServerBase: function(postfix) {
+ return buildServerApiUrl.createSlugApiServerBase(ctx.tplugin.tmeta.key,postfix);
+ },
+ createSlugApiPluginBase: function(postfix) {
+ return buildServerApiUrl.createSlugApiPluginBase(ctx.tplugin.tmeta.key,postfix);
+ },
+
+ hostFileCSSLocal: function (opt) {
+ hostFile(ctx,opt,ctx.tplugin.tmeta.localDir,'css');
+ },
+ hostFileJSLocal: function (opt) {
+ hostFile(ctx,opt,ctx.tplugin.tmeta.localDir,'js');
+ },
+
+
+ hostFileCSSFontNodeModule: function (opt) {
+ hostFileCssFont(ctx,opt,__dirname+'/../node_modules/');
+ },
+ hostFileCSSNodeModule: function (opt) {
+ hostFile(ctx,opt,__dirname+'/../node_modules/','css');
+ },
+ hostFileJSNodeModule: function (opt) {
+ hostFile(ctx,opt,__dirname+'/../node_modules/','js');
+ },
+
+ hostTemplateJS: function(templateFile, skipReg) {
+ hostTemplate(ctx,templateFile, 'text/javascript', skipReg);
+ },
+ hostTemplateHTML: function(templateFile, skipReg) {
+ hostTemplate(ctx,templateFile, 'text/html', skipReg);
+ },
+ hostTemplateCSS: function(templateFile, skipReg) {
+ hostTemplate(ctx,templateFile, 'text/css', skipReg);
+ },
+
+ renderRedirect: buildServerExpress.renderRedirect,
+ renderFunctionJSON: buildServerExpress.renderFunctionJSON,
+ renderTemplate: function (templateFile, contentType) {
+ return renderTemplate(ctx.tplugin.tmeta.key,templateFile, contentType);
+ }
+ }
+ buildServerExpress.saveRoutes(server);
+ configRegistry.pluginCall('configServer',ctx, function(plugin) {
+ buildServerExpress.saveRoutes(server);
+ Array.prototype.push.apply(plugin['troutes'],buildServerExpress.getRoutesDiff(server));
+ });
+ }
+
+ return function BuildServerApi() {
+
+ this.build = function(server) {
+ debug('buildServerApi');
+ configRegistry.assertPhaseServer();
+
+ var cors = require('cors');
+ server.use(cors({credentials: true, origin: '*'}));
+ //troot.tmeta.tserver.tapi.cors
+
+ buildCrudApi(server);
+ };
+ };
+})();
+
+module.exports = new mod();
diff --git a/lib/build-server-express.js b/lib/build-server-express.js
new file mode 100644
index 0000000..3d41b20
--- /dev/null
+++ b/lib/build-server-express.js
@@ -0,0 +1,99 @@
+var debug = require('debug')('ff:tcrud:build:server:express');
+
+var mod = (function () {
+
+ var walkRouteTree = function(result,stack,parent_path) {
+ if (parent_path == null) {
+ parent_path = '';
+ }
+ stack.forEach(function(middleware) {
+ if (middleware.route){
+ var path = parent_path + middleware.route.path;
+ for (var httpMethod in middleware.route.methods) {
+ var data = {
+ uriPath: path,
+ httpMethod: httpMethod
+ };
+ result.push(data);
+ }
+ } else if (middleware.name === 'router') {
+ var pp = parent_path + middleware.handle.path;
+ walkRouteTree(result,middleware.handle.stack,pp);
+ } else {
+ //log.info('route err: '+JSON.stringify(middleware));
+ }
+ });
+ return result;
+ }
+
+ var buildRouteLine = function(routes) {
+ var result = [];
+ for (var routeIdx in routes) {
+
+ }
+ return result;
+ }
+
+ var buildRouteDiff = function(obj,objOld) {
+ var result = [];
+ for (var objIdx in obj) {
+ var objResult = obj[objIdx];
+ var objAdd = true;
+ for (var objOldIdx in objOld) {
+ var objOldResult = objOld[objOldIdx];
+
+ if (objResult.uriPath === objOldResult.uriPath && objResult.httpMethod === objOldResult.httpMethod) {
+ objAdd = false;
+ break;
+ }
+ }
+ if (objAdd) {
+ result.push(objResult);
+ }
+ }
+ return result;
+ }
+
+ return function BuildServerExpress() {
+
+ this.saveRoutes = function(server) {
+ if (!server) {
+ throw new Error('no server given');
+ }
+ var resultOld = server.get('ff_tcrud_route_list');
+ var result = walkRouteTree([],server._router.stack);
+ result.sort(function(a, b) {
+ return a.uriPath.localeCompare(b.uriPath);
+ });
+ server.set('ff_tcrud_route_list',result);
+ server.set('ff_tcrud_route_list_diff',buildRouteDiff(result,resultOld));
+ };
+
+ this.getRoutes = function(server) {
+ return server.get('ff_tcrud_route_list');
+ };
+
+ this.getRoutesDiff = function(server) {
+ return server.get('ff_tcrud_route_list_diff');
+ };
+
+ this.renderRedirect = function(location) {
+ if (!location) {
+ throw 'no location';
+ }
+ return function(req, res) {
+ res.redirect(location);
+ };
+ };
+
+ this.renderFunctionJSON = function(fn) {
+ return function (req, res, next) {
+ res.json({
+ data: fn()
+ });
+ };
+ };
+ };
+})();
+
+module.exports = new mod();
diff --git a/lib/config-registry.js b/lib/config-registry.js
new file mode 100644
index 0000000..889abec
--- /dev/null
+++ b/lib/config-registry.js
@@ -0,0 +1,405 @@
+var path = require('path');
+var fs = require('fs');
+var debug = require('debug')('ff:tcrud:config:registry');
+var configUtil = require('./config-util');
+
+var mod = (function () {
+
+ var phaseInit = true;
+ var phaseConfig = false;
+ var phaseServer = false;
+ var phaseServerUp = false;
+
+ // Master template for plugins.
+ this.masterTemplates = {
+ masterTFieldTemplate: {},
+ masterTFieldTHelp: {},
+ masterTEntityTemplate: {},
+ masterTEntityTHelp: {},
+ masterTViewTemplate: {},
+ masterTViewTHelp: {}
+ };
+
+ // Master config of tcrud
+ this.masterConfig = {
+ rootTEntity: null,
+ rootTMenu: {},
+ plugins: [],
+ backends: {},
+ validators: {},
+ clientResources: {
+ js: [],
+ css: [],
+ dss: []
+ }
+ };
+
+ this.pluginKeysAllowed = [
+ 'configPlugin',
+ 'configTemplate',
+ 'configServer',
+ 'configApi',
+ 'configApiTListExport',
+ 'configApiTCreateExport',
+ 'configApiTReadExport',
+ 'configApiTEditExport',
+ 'configApiTDeleteExport',
+ 'configApiTCountExport',
+ 'configPostBoot',
+ 'fillTEntity',
+ 'fillTField',
+ 'fillTView',
+ 'createBackend',
+ 'key','dbModule','query','conn'];
+
+
+ var mergeMaster = function(objectDest,pluginKey) {
+ return function(objectSrc) {
+ //console.log('mergeMasterSrc:'+JSON.stringify(objectSrc));
+ //console.log('mergeMasterDst:'+JSON.stringify(objectDest));
+
+ var resPre = {keys: 0,values: 0};
+ configUtil.countTree(objectDest,resPre);
+
+ configUtil.copyByTemplate('',objectDest,objectSrc,objectSrc);
+
+ var resPost = {keys: 0,values: 0};
+ configUtil.countTree(objectDest,resPost);
+
+ // TODO: check pre+post counters so first 2 arrays grow equal !
+
+ debug('mergeMaster'
+ +' start: '+resPre.keys+' -> '+resPre.values
+ +' end: '+resPost.keys+' -> '+resPost.values
+ +' diff: '+(resPost.keys-resPre.keys)+' -> '+(resPost.values-resPre.values)
+ +' plugin: '+pluginKey);
+ }
+ };
+
+ var tentityMergeDown = function(prefix,objectDest,objectSrc) {
+ var keys = Object.keys(objectSrc);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var value = objectSrc[key];
+ if (!value) {
+ //console.log(prefix+'.'+key+' src object has no value.');
+ continue; // no src value
+ }
+ if (!objectDest[key]) {
+ objectDest[key] = configUtil.clone(value);
+ } else if (typeof value === 'object') {
+
+ if (key === 'tchilds') {
+ continue;
+ }
+ //console.log(prefix+'.'+key+' going deeper');
+ tentityMergeDown(prefix+'.'+key,objectDest[key],value)
+ }
+ }
+ };
+
+ var tentityFillTree = function(parent) {
+ debug('tentityFillTree.tid: %s',parent.tid);
+ for (var key in parent.tmeta.tfields) {
+ pluginCall('fillTField',{
+ filterValue: function(a,b,c,d) {return configUtil.filterValue(a,b,c,d);},
+ tfield: parent.tmeta.tfields[key]
+ });
+ }
+ for (var i = 0; i < parent.tchilds.length; i++) {
+ var child = parent.tchilds[i];
+ tentityMergeDown('',child,parent);
+ tentityFillTree(child);
+ }
+ pluginCall('fillTEntity',{
+ filterValue: function(a,b,c,d) {return configUtil.filterValue(a,b,c,d);},
+ tentity: parent
+ });
+ };
+
+ var pluginCall = function(fnName,ctx,afterPluginCb) {
+ var startTime = new Date().getTime();
+ var callCount = 0;
+ var keys = Object.keys(masterConfig.plugins);
+ for (var i = 0; i < keys.length; i++) {
+ var pluginKey = keys[i];
+ var plugin = masterConfig.plugins[pluginKey];
+ if (plugin.tenable === false) {
+ continue;
+ }
+ ctx.tplugin = plugin;
+ if (plugin[fnName]) {
+ var result = plugin[fnName](ctx);
+ callCount++;
+ if (afterPluginCb !== undefined) {
+ afterPluginCb(plugin, result);
+ }
+ }
+ }
+ debug('pluginCall.function: %s done %s plugins in %s ms.',fnName,callCount,(new Date().getTime()-startTime));
+ };
+
+ var assertPhaseInit = function() {
+ if (!phaseInit) {
+ throw Error('Not in init phase.')
+ }
+ };
+
+ var assertPhaseConfig = function() {
+ if (!phaseConfig) {
+ throw Error('Not in config phase.')
+ }
+ };
+
+ var assertPhaseServer = function() {
+ if (!phaseServer) {
+ throw Error('Not in server phase.')
+ }
+ };
+
+ var assertPhaseServerUp = function() {
+ if (!phaseServerUp) {
+ throw Error('Not in serverUp phase.')
+ }
+ };
+
+ var pluginLoad = function(plugin) {
+ assertPhaseInit();
+ if (plugin === undefined) {
+ throw new Error('No object given');
+ }
+ if (plugin.configPlugin === undefined) {
+ throw new Error('Object is not a plugin: Missing configPlugin()');
+ }
+ // run config to have key
+ var ctx = {
+ key: null,
+ description: null,
+ dependencies: [],
+ localDir: null,
+ localConfigTemplate: null
+ }
+ plugin.configPlugin(ctx);
+ if (ctx.key === null) {
+ throw new Error('Plugin does not provide pluginKey.');
+ }
+
+ // Validate plugin before injection
+ for (var objectKey in plugin) {
+ if (objectKey.charAt(0) === '_') {
+ continue; // tmp
+ }
+ if (pluginKeysAllowed.indexOf(objectKey) !== -1) {
+ continue;
+ }
+ throw new Error('Illegal plugin objectKey: '+objectKey+' plugin: '+plugin.tmeta.key);
+ }
+
+ plugin['tmeta'] = ctx;
+ plugin['troutes'] = [];
+ plugin['tenable'] = false;
+
+ if (ctx.localConfigTemplate) {
+ var pluginJsonFile = path.join(ctx.localDir,ctx.localConfigTemplate);
+ var pluginJsonTemplate = fs.readFileSync(pluginJsonFile, 'utf8');
+ plugin['configTemplateLocal'] = function (ctx) {
+ debug('configTemplateLocal file %s',pluginJsonFile);
+ ctx.mergeMaster(JSON.parse(pluginJsonTemplate));
+ };
+ debugPostfix = ' localConfigTemplate:\t'+pluginJsonFile;
+ }
+ //masterConfig.plugins[plugin.tmeta.key]=plugin;
+ masterConfig.plugins.push(plugin);
+ debug('pluginLoad %s',plugin.tmeta.key);
+ };
+
+ var pluginLoadTree = function (plugins) {
+ var keys = Object.keys(plugins);
+ var result = [];
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var value = plugins[key];
+
+ if (typeof value === 'function') {
+ pluginLoad(new value());
+ } else {
+ pluginLoadTree(value);
+ }
+ }
+ };
+
+ var pluginEnable = function(pluginFilter, value) {
+ assertPhaseInit();
+ if (pluginFilter === undefined) {
+ throw new Error('No pluginKey given');
+ }
+ if (value === undefined) {
+ value = true;
+ }
+ var keys = Object.keys(masterConfig.plugins);
+ for (var i = 0; i < keys.length; i++) {
+ var pluginIdx = keys[i];
+ var plugin = masterConfig.plugins[pluginIdx];
+ var pluginKey = plugin.tmeta.key;
+
+ if (pluginKey.match(pluginFilter)) {
+ debug('pluginEnable plugin: '+pluginKey+' value: '+value);
+ plugin.tenable = value;
+ }
+ }
+ };
+
+ var pluginIndexOf = function(checkKey) {
+ var keys = Object.keys(masterConfig.plugins);
+ for (var i = 0; i < keys.length; i++) {
+ var pluginKey = keys[i];
+ var plugin = masterConfig.plugins[pluginKey];
+ if (plugin.tmeta.key === checkKey) {
+ return i;
+ }
+ }
+ return -1;
+ }
+
+ var pluginOrder = function() {
+
+ var keys = Object.keys(masterConfig.plugins);
+ for (var pluginId = 0; pluginId < keys.length; pluginId++) {
+ var pluginKey = keys[pluginId];
+ var plugin = masterConfig.plugins[pluginKey];
+ if (plugin.tmeta.dependencies.length === 0) {
+ continue;
+ }
+ for (var depId in plugin.tmeta.dependencies) {
+ var dep = plugin.tmeta.dependencies[depId];
+ var depIdx = pluginIndexOf(dep);
+
+
+ if (depIdx === -1) {
+ throw new Error('Missing plugin: '+dep+' requested by: '+plugin.tmeta.key);
+ }
+ if (depIdx < pluginId) {
+ continue;
+ }
+ debug('pluginOrder dependency move: '+dep+' before: '+plugin.tmeta.key);
+
+ var depMove = masterConfig.plugins[depIdx];
+ var oldPlugins = masterConfig.plugins;
+ var newPlugins = [];
+
+ newPlugins = oldPlugins.slice(0,pluginId);
+ newPlugins.push(depMove);
+ newPlugins = newPlugins.concat(oldPlugins.slice(pluginId,depIdx));
+ newPlugins = newPlugins.concat(oldPlugins.slice(depIdx+1));
+
+ masterConfig.plugins = newPlugins;
+ return false;
+ }
+ }
+ return true;
+ }
+
+ return function ConfigRegistry() {
+
+ this.assertPhaseInit = assertPhaseInit;
+ this.assertPhaseConfig = assertPhaseConfig;
+ this.assertPhaseServer = assertPhaseServer;
+ this.assertPhaseServerUp = assertPhaseServerUp;
+
+ this.pluginCall = pluginCall;
+ this.pluginLoad = pluginLoad;
+ this.pluginLoadTree = pluginLoadTree;
+ this.pluginEnable = pluginEnable;
+
+ this.phaseNext = function() {
+ if (phaseInit) {
+ debug('phaseNext init->config');
+ phaseInit = false;
+ phaseConfig = true;
+
+ debug('pluginOrder start');
+ while (!pluginOrder()) {
+ }
+ debug('pluginOrder done');
+
+ var ctx = {
+ mergeMaster: function(template) {
+ mergeMaster(masterTemplates,ctx.tplugin.tmeta.key)(template);
+ }
+ };
+ pluginCall('configTemplate',ctx);
+ pluginCall('configTemplateLocal',ctx);
+ pluginCall('createBackend',{},function (plugin,backend) {
+ masterConfig.backends[backend.getKey()]=backend;
+ debug('createBackend: %s',backend.getKey());
+ });
+
+ } else if (phaseConfig) {
+ debug('phaseNext config->server');
+ phaseConfig = false;
+ phaseServer = true;
+
+ tentityFillTree(masterConfig.rootTEntity);
+
+ } else if (phaseServer) {
+ debug('phaseNext server->serverUp');
+ phaseServer = false;
+ phaseServerUp = true;
+
+ } else if (phaseServerUp) {
+ debug('phaseNext serverUp->done');
+ phaseServerUp = false;
+
+ } else {
+ throw new Error('All phases are done.');
+ }
+ };
+
+ this.getRootTEntity = function() {
+ if (masterConfig.rootTEntity === null) {
+ masterConfig.rootTEntity = configUtil.clone(masterTemplates.masterTEntityTemplate);
+ masterConfig.rootTEntity.tid = 'root';
+ debug('getRootTEntity.created');
+ }
+ return masterConfig.rootTEntity;
+ };
+
+ this.getMasterTemplates = function() {
+ return masterTemplates;
+ };
+
+ this.getMasterConfig = function() {
+ return masterConfig;
+ }
+
+ this.registrateClientResource = function(clientResource, resourceType) {
+ if (clientResource === undefined) {
+ throw new Error('No resource provided');
+ }
+ if (resourceType === undefined) {
+ throw new Error('No resource provided');
+ }
+ debug('registrateClientResource: '+clientResource+' type: '+resourceType);
+ masterConfig.clientResources[resourceType].push(clientResource);
+ };
+
+ this.createClientResourceFetchList = function() {
+ var fetchList = [];
+ for (var clientResourceIdx in masterConfig.clientResources.js) {
+ var url = masterConfig.clientResources.js[clientResourceIdx];
+ fetchList.push({url:url,type:'js'});
+ }
+ for (var clientResourceIdx in masterConfig.clientResources.css) {
+ var url = masterConfig.clientResources.css[clientResourceIdx];
+ fetchList.push({url:url,type:'css'});
+ }
+ for (var clientResourceIdx in masterConfig.clientResources.dss) {
+ var url = masterConfig.clientResources.dss[clientResourceIdx];
+ fetchList.push({url:url,type:'dss'});
+ }
+ return fetchList;
+ };
+ };
+})();
+
+module.exports = new mod();
diff --git a/lib/config-util.js b/lib/config-util.js
new file mode 100644
index 0000000..f8dd4ba
--- /dev/null
+++ b/lib/config-util.js
@@ -0,0 +1,100 @@
+var cloneImpl = require('clone');
+var debug = require('debug')('ff:tcrud:config:util');
+var configRegistry = require('./config-registry');
+
+var mod = (function () {
+
+ var clone = function(obj) {
+ return cloneImpl(obj);
+ };
+
+ var filterValuePart = function(value,replaceChar,splitChar,partFn) {
+ var result = '';
+ var parts = value.split(splitChar);
+ for (var i = 0; i < parts.length; i++) {
+ var part = parts[i];
+ if (part.length > 1) {
+ result += partFn(part);
+ } else {
+ result += part;
+ }
+ if (i < parts.length - 1) {
+ result += replaceChar;
+ }
+ }
+ return result;
+ };
+
+ var copyByTemplate = function(prefix,objectDest,objectSrc,copyTemplate) {
+ var keys = Object.keys(copyTemplate);
+ var result = [];
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var value = objectSrc[key];
+ if (value === null) {
+ //debug('copyByTemplate '+prefix+'.'+key+' src object has null value.');
+ objectDest[key] = null;
+ continue; // no src value
+ }
+ var templateValue = copyTemplate[key];
+ if (templateValue === null) {
+ if (objectDest[key] !== null) {
+ //debug('copyByTemplate '+prefix+'.'+key+' has own value: '+objectDest[key]);
+ continue;
+ } else {
+ //debug('copyByTemplate '+prefix+'.'+key+' copy value: '+value);
+ objectDest[key] = clone(value);
+ }
+ } else {
+
+ if (objectDest[key] === undefined) {
+ objectDest[key] = clone(templateValue);
+ } else {
+ //debug('copyByTemplate '+prefix+'.'+key+' going deeper');
+ copyByTemplate(prefix+'.'+key,objectDest[key],value,templateValue)
+ }
+ }
+ }
+ };
+
+ var countTree = function(obj,result) {
+ if (typeof obj === 'object') {
+ for (var key in obj) {
+ result.keys++;
+ countTree(obj[key],result)
+ }
+ } else {
+ result.values++;
+ }
+ };
+
+ return function ConfigUtil() {
+
+ this.countTree = countTree;
+
+ this.copyByTemplate = copyByTemplate;
+
+ this.clone = clone;
+
+ this.filterValue = function(value,removeChars,replaceChar,partFn) {
+ var valueOrg = value;
+ for (var i = 0; i < removeChars.length; i++) {
+ value = filterValuePart(value,replaceChar,removeChars[i],partFn);
+ }
+ //debug('filterValue.result: %s from: %s',value,valueOrg);
+ return value;
+ };
+
+ this.stringHash = function (str) {
+ var hash = 31; // prime
+ for (var i = 0; i < str.length; i++) {
+ hash = ((hash<<5)-hash)+str.charCodeAt(i);
+ hash = hash & hash; // keep 32b
+ }
+ return hash;
+ };
+
+ };
+})();
+
+module.exports = new mod();
diff --git a/lib/default/default-tentity.js b/lib/default/default-tentity.js
new file mode 100644
index 0000000..caaf747
--- /dev/null
+++ b/lib/default/default-tentity.js
@@ -0,0 +1,28 @@
+
+module.exports = (function () {
+
+ return function DefaultTEntityPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'defaultTEntity';
+ ctx.description = 'Adds the basic data and structure of the tentity.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'default-tentity.json';
+ };
+
+ this.fillTEntity = function(ctx) {
+ if (ctx.tentity === undefined) {
+ throw new Error('no tentity');
+ }
+ if (ctx.tentity.tid === undefined) {
+ throw new Error('no tentity.tid');
+ }
+ if (ctx.tentity.tid === null) {
+ throw new Error('null tentity.tid');
+ }
+ if (ctx.tentity.tid.length === 0) {
+ throw new Error('empty tentity.tid');
+ }
+ };
+ };
+})();
diff --git a/lib/default/default-tentity.json b/lib/default/default-tentity.json
new file mode 100644
index 0000000..93cec80
--- /dev/null
+++ b/lib/default/default-tentity.json
@@ -0,0 +1,83 @@
+{
+ "masterTEntityTemplate": {
+ "tid": null,
+ "tname": null,
+ "tplural": null,
+ "tslug": null,
+ "tenable": null,
+ "tcode": null,
+ "tkeys": [],
+ "troles": [],
+ "tparent": null,
+ "tchilds": [],
+ "tlist": {
+ "tenable": null,
+ "ttext": "Listing of ..",
+ "troles": [],
+ "tmethods": ["GET"],
+ "tplugin": {}
+ },
+ "tcreate": {
+ "tenable": null,
+ "ttext": null,
+ "troles": [],
+ "tmethods": ["POST"],
+ "tplugin": {}
+ },
+ "tedit": {
+ "tenable": null,
+ "ttext": null,
+ "troles": [],
+ "tmethods": ["PUT","POST"],
+ "tplugin": {}
+ },
+ "tread": {
+ "tenable": null,
+ "ttext": null,
+ "troles": [],
+ "tslug": null,
+ "tmethods": ["GET"],
+ "tplugin": {}
+ },
+ "tdelete": {
+ "tenable": null,
+ "ttext": null,
+ "troles": [],
+ "tmethods": ["DELETE","POST"],
+ "tplugin": {}
+ },
+ "tcount": {
+ "tenable": null,
+ "ttext": "Count records",
+ "troles": [],
+ "tmethods": ["GET"],
+ "tplugin": {}
+ },
+ "tverify": {
+ "tenable": null,
+ "ttext": "Verify data",
+ "troles": [],
+ "tmethods": ["POST"],
+ "tplugin": {}
+ },
+ "tmeta": {
+ "tmodel": {
+ "tkey": null,
+ "tkeys": [],
+ "tid": null,
+ "tbackend": null
+ },
+ "tmenu": {
+ "tenable": null,
+ "tkey": null,
+ "tname": null,
+ "titem": null,
+ "ticon": null
+ },
+ "troles": [],
+ "tfields": {},
+ "tplugin": {}
+ }
+ },
+ "masterTEntityTHelp": {}
+}
\ No newline at end of file
diff --git a/lib/default/default-tfield.js b/lib/default/default-tfield.js
new file mode 100644
index 0000000..5042960
--- /dev/null
+++ b/lib/default/default-tfield.js
@@ -0,0 +1,32 @@
+
+module.exports = (function () {
+
+ return function DefaultTFieldPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'defaultTField';
+ ctx.description = 'Adds the basic data and structure of the tfield.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'default-tfield.json';
+ };
+
+ this.fillTField = function(ctx) {
+ if (ctx.tfield === undefined) {
+ throw new Error('no tfield');
+ }
+ if (ctx.tfield.tid === undefined) {
+ throw new Error('no tfield.tid');
+ }
+ if (ctx.tfield.tid === null) {
+ throw new Error('null tfield.tid');
+ }
+ if (ctx.tfield.tid.length === 0) {
+ throw new Error('empty tfield.tid');
+ }
+ if (ctx.tfield.ttype === null) {
+ ctx.tfield.ttype = 'text'; // move ?
+ }
+ };
+ };
+})();
+
diff --git a/lib/default/default-tfield.json b/lib/default/default-tfield.json
new file mode 100644
index 0000000..b438d30
--- /dev/null
+++ b/lib/default/default-tfield.json
@@ -0,0 +1,54 @@
+{
+ "masterTFieldTemplate": {
+ "tid": null,
+ "tname": null,
+ "ttext": null,
+ "ttype": null,
+ "tslug": null,
+ "tvalidate": {
+ "io": null
+ },
+ "tlist": {
+ "tenable": true,
+ "troles": []
+ },
+ "tread": {
+ "tenable": true,
+ "troles": []
+ },
+ "tedit": {
+ "tenable": true,
+ "troles": []
+ },
+ "tcreate": {
+ "tenable": true,
+ "troles": []
+ }
+ },
+ "masterTFieldTHelp": {
+ "tid": "The id of the field.",
+ "tname": "The name of the field.",
+ "ttext": "The description of the field.",
+ "ttype": "The field type",
+ "tslug": "The slug of the field.",
+ "tvalidate": {
+ "io": "Named validate-io rules for the field."
+ },
+ "tlist": {
+ "tenable": "Is field enabled in crud listing.",
+ "troles": "Needed roles for listing."
+ },
+ "tread": {
+ "tenable": "Is field enabled in crud reading.",
+ "troles": "Needed roles for reading."
+ },
+ "tedit": {
+ "tenable": "Is field enabled in crud editing.",
+ "troles": "Needed roles for editing."
+ },
+ "tcreate": {
+ "tenable": "Is field enabled in crud creating.",
+ "troles": "Needed roles for creating."
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/default/default-tserver.js b/lib/default/default-tserver.js
new file mode 100644
index 0000000..f7df169
--- /dev/null
+++ b/lib/default/default-tserver.js
@@ -0,0 +1,13 @@
+
+module.exports = (function () {
+
+ return function DefaultTServerPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'defaultTServer';
+ ctx.description = 'Adds the basic data and structure of the tserver.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'default-tserver.json';
+ };
+ };
+})();
diff --git a/lib/default/default-tserver.json b/lib/default/default-tserver.json
new file mode 100644
index 0000000..9c2a3d2
--- /dev/null
+++ b/lib/default/default-tserver.json
@@ -0,0 +1,44 @@
+{
+ "masterTEntityTemplate": {
+ "tlist": { "tserver": {
+ "tpipe": {
+ "query": {
+ "defaults": { "limit": 10 },
+ "maxes": { "limit": 50 }
+ }
+ }
+ }},
+ "tmeta": { "tserver": {
+ "thost": "http://localhost:8080",
+ "tslugs": {
+ "tbase": "/api",
+ "tserver": "server",
+ "tentity": "tentity",
+ "tplugin": "plugin"
+ },
+ "tpopfix": true,
+ "tformats": ["formatJSON","formatXML","formatCSV","serverConfigTView","angular"],
+ "tapi": {
+ "cors": true,
+ "timeoute": 30000
+ }
+ }}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tserver": {
+ "thost": "The host of the server.",
+ "tslugs": {
+ "tbase": "The base api prefix slug",
+ "tserver": "The server slug",
+ "tentity": "The tentity slug",
+ "tplugin": "The plugin slug"
+ },
+ "tpopfix": "Use tech or format first.",
+ "tformats": "list of exported formats.",
+ "tapi": {
+ "cors": "Allow cors requests on api.",
+ "timeoute": "Timeout of api requests."
+ }
+ }}
+ }
+}
\ No newline at end of file
diff --git a/lib/default/default-tview.js b/lib/default/default-tview.js
new file mode 100644
index 0000000..0ef1ac2
--- /dev/null
+++ b/lib/default/default-tview.js
@@ -0,0 +1,38 @@
+
+module.exports = (function () {
+
+ var addFilteredFields = function(tview,type,tcrud) {
+ //debug('addFilterFields of: '+type+' from: '+tview.tid);
+ result = [];
+ var keys = Object.keys(tcrud.tmeta.tfields);
+ var result = [];
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var tfield = tcrud.tmeta.tfields[key];
+ if (tfield[type]) {
+ if (!tfield[type].tenable) {
+ continue;
+ }
+ }
+ result.push(key); // default is true..
+ }
+ tview[type].tfields = result;
+ };
+
+ return function DefaultTViewPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'defaultTView';
+ ctx.description = 'Add copy template for building the tview from the tentity.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'default-tview.json';
+ };
+
+ this.fillTView = function(ctx) {
+ addFilteredFields(ctx.tview,'tlist',ctx.tentity);
+ addFilteredFields(ctx.tview,'tread',ctx.tentity);
+ addFilteredFields(ctx.tview,'tedit',ctx.tentity);
+ addFilteredFields(ctx.tview,'tcreate',ctx.tentity);
+ };
+ };
+})();
diff --git a/lib/default/default-tview.json b/lib/default/default-tview.json
new file mode 100644
index 0000000..3acb936
--- /dev/null
+++ b/lib/default/default-tview.json
@@ -0,0 +1,135 @@
+{
+ "masterTViewTemplate": {
+ "tid": null,
+ "tname": null,
+ "tplural": null,
+ "tslug": null,
+ "tcode": null,
+ "tenable": null,
+ "troles": null,
+ "tkeys": [],
+ "tcount": {
+ "ttext": null,
+ "tenable": null,
+ "troles": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tlist": {
+ "tfields": [],
+ "ttext": null,
+ "tlinks": null,
+ "tenable": null,
+ "troles": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tread": {
+ "tfields": [],
+ "ttext": null,
+ "tenable": null,
+ "troles": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tedit": {
+ "tfields": [],
+ "ttext": null,
+ "tenable": null,
+ "troles": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tcreate": {
+ "tfields": [],
+ "ttext": null,
+ "tenable": null,
+ "troles": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tdelete": {
+ "ttext": null,
+ "tenable": null,
+ "troles": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tverify": {
+ "ttext": null,
+ "tenable": null,
+ "troles": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tmeta": {
+ "tmodel": null,
+ "tfields": null,
+ "tvalidate": null,
+ "tserver": null,
+ "tplugin": null,
+ "tmenu": null
+ }
+ },
+ "masterTViewTHelp": {
+ "tid": "The name of the view.",
+ "tplural": "The plural name of the view.",
+ "tslug": "The slug of the view.",
+ "tcode": null,
+ "tkeys": "The primary key fields.",
+ "tcount": {
+ "ttext": null,
+ "tenable": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tlist": {
+ "tfields": [],
+ "ttext": null,
+ "tlinks": null,
+ "tenable": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tread": {
+ "tfields": [],
+ "ttext": null,
+ "tenable": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tedit": {
+ "tfields": [],
+ "ttext": null,
+ "tenable": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tcreate": {
+ "tfields": [],
+ "ttext": null,
+ "tenable": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tdelete": {
+ "ttext": null,
+ "tenable": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tverify": {
+ "ttext": null,
+ "tenable": null,
+ "tmethods": null,
+ "tplugin": null
+ },
+ "tmeta": {
+ "tmodel": null,
+ "tfields": null,
+ "tvalidate": null,
+ "tserver": null,
+ "tplugin": null
+ }
+ }
+}
\ No newline at end of file
diff --git a/lib/node-ff-tcrud.js b/lib/node-ff-tcrud.js
index f571f68..30f3a24 100644
--- a/lib/node-ff-tcrud.js
+++ b/lib/node-ff-tcrud.js
@@ -1,6 +1,26 @@
-'use strict';
+var debug = require('debug')('ff:tcrud');
+var startTime = new Date().getTime();
+debug('Init welcome');
-var assets = {
+debug('Init objects...');
+var requireAll = require('require-all');
+var configRegistry = require('./config-registry');
+var tcrudSetup = require('./tcrud-setup');
+var tcrudConfig = require('./tcrud-config');
+debug('Init objects done');
+
+debug('Init plugins...');
+configRegistry.pluginLoadTree(requireAll({dirname: __dirname + '/default',filter: /^([^\.].*)\.js?$/}));
+configRegistry.pluginLoadTree(requireAll({dirname: __dirname + '/plugin',filter: /^([^\.].*)\.js?$/}));
+debug('Init plugins done');
+
+debug('Init backends...');
+var tcrudBackend = requireAll({dirname: __dirname + '/backend',filter: /^([^\.].*)\.js?$/});
+debug('Init backends done');
+debug('Init done in %s ms.',(new Date().getTime()-startTime));
+
+module.exports = {
+ setup: tcrudSetup,
+ config: tcrudConfig,
+ backend: tcrudBackend
};
-
-module.exports = assets;
diff --git a/lib/plugin/auto/auto-tenable.js b/lib/plugin/auto/auto-tenable.js
new file mode 100644
index 0000000..ad62e12
--- /dev/null
+++ b/lib/plugin/auto/auto-tenable.js
@@ -0,0 +1,54 @@
+
+module.exports = (function () {
+
+ var autoFieldEnable = function(tfield,type) {
+ if (type === undefined) {
+ throw new Error('no type');
+ }
+ if (tfield[type] === undefined) {
+ tfield[type] = {}; // todo rm this
+ }
+ if (tfield[type].tenable !== null) {
+ return;
+ }
+ var fieldKey = tfield.tid;
+ var result = true;
+ if ('tlist' === type) {
+ var name = fieldKey.toLowerCase();
+ if (fieldKey.indexOf('description') >= 0) {
+ result = false;
+ } else if (fieldKey.indexOf('comment') >= 0) {
+ result = false;
+ }
+ }
+ tfield[type].tenable = result;
+ }
+
+ return function AutoTEnablePlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='autoTEnable';
+ ctx.description='Auto enables to true if undefined.';
+ };
+
+ this.fillTEntity = function(ctx) {
+ if (ctx.tentity.tenable === null || ctx.tentity.tenable === undefined) {
+ ctx.tentity.tenable = true;
+ }
+ autoFieldEnable(ctx.tentity,'tlist');
+ autoFieldEnable(ctx.tentity,'tread');
+ autoFieldEnable(ctx.tentity,'tedit');
+ autoFieldEnable(ctx.tentity,'tcreate');
+ autoFieldEnable(ctx.tentity,'tdelete');
+ autoFieldEnable(ctx.tentity,'tcount');
+ autoFieldEnable(ctx.tentity,'tverify');
+ }
+
+ this.fillTField = function(ctx) {
+ autoFieldEnable(ctx.tfield,'tlist');
+ autoFieldEnable(ctx.tfield,'tread');
+ autoFieldEnable(ctx.tfield,'tedit');
+ autoFieldEnable(ctx.tfield,'tcreate');
+ }
+ };
+})();
diff --git a/lib/plugin/auto/auto-tmenu.js b/lib/plugin/auto/auto-tmenu.js
new file mode 100644
index 0000000..77f945c
--- /dev/null
+++ b/lib/plugin/auto/auto-tmenu.js
@@ -0,0 +1,45 @@
+
+module.exports = (function () {
+
+ return function AutoTMenuPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='autoTMenu';
+ ctx.description='Auto enables menu items.';
+ ctx.dependencies.push('autoTEntityTPlural');
+ };
+
+ this.fillTEntity = function(ctx) {
+ if (ctx.tentity.tparent === null) {
+ return; // root node
+ }
+ if (ctx.tentity.tmeta.tmodel.tid === null) {
+ ctx.tentity.tmeta.tmenu.titem = false; // auto menu for items
+ }
+
+ if (ctx.tentity.tmeta.tmenu.tenable === null || ctx.tentity.tmeta.tmenu.tenable === undefined) {
+ ctx.tentity.tmeta.tmenu.tenable = true;
+ }
+ if (ctx.tentity.tmeta.tmenu.titem === null || ctx.tentity.tmeta.tmenu.titem === undefined) {
+ ctx.tentity.tmeta.tmenu.titem = true;
+ }
+ if (ctx.tentity.tmeta.tmenu.tname === null || ctx.tentity.tmeta.tmenu.tname === undefined) {
+ ctx.tentity.tmeta.tmenu.tname = ctx.tentity.tplural.substring(0,1).toUpperCase()+ctx.tentity.tplural.substring(1);
+ }
+ if (ctx.tentity.tmeta.tmenu.tkey === null || ctx.tentity.tmeta.tmenu.tkey === undefined) {
+ if (ctx.tentity.tmeta.tmenu.titem) {
+ ctx.tentity.tmeta.tmenu.tkey = ctx.tentity.tparent.tid;
+ } else {
+ ctx.tentity.tmeta.tmenu.tkey = ctx.tentity.tid;
+ }
+ }
+ if (ctx.tentity.tmeta.tmenu.ticon === null || ctx.tentity.tmeta.tmenu.ticon === undefined) {
+ if (ctx.tentity.tmeta.tmenu.titem) {
+ ctx.tentity.tmeta.tmenu.ticon = 'fa fa-table';
+ } else {
+ ctx.tentity.tmeta.tmenu.ticon = 'fa fa-cubes';
+ }
+ }
+ }
+ };
+})();
diff --git a/lib/plugin/auto/auto-tname.js b/lib/plugin/auto/auto-tname.js
new file mode 100644
index 0000000..2a24e18
--- /dev/null
+++ b/lib/plugin/auto/auto-tname.js
@@ -0,0 +1,50 @@
+
+module.exports = (function () {
+
+ var createName = function (fieldKey,fieldName) {
+ if (fieldKey === undefined) {
+ throw new Error('no fieldKey');
+ }
+ if (fieldName && fieldName.length !== 0) {
+ return fieldName;
+ }
+ var result = '';
+ var names = fieldKey.split('_');
+ for (var i in names) {
+ var name = names[i];
+ if (name.length > 1) {
+ name = name.substring(0,1).toUpperCase() + name.substring(1);
+ }
+ result = result + ' ' + name;
+ }
+ return result.substring(1); // remove first space
+ };
+
+ var filterName = function(ctx,value) {
+ return ctx.filterValue(value,['_','-','.',','],' ', function (part) {
+ return part.substring(0,1).toUpperCase()+part.substring(1).toLowerCase();
+ });
+ };
+
+ return function AutoTNamePlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='autoTName';
+ ctx.description='Auto fills and checks the tname fields.';
+ };
+
+ this.fillTField = function(ctx) {
+ if (ctx.tfield.tname === null) {
+ ctx.tfield.tname = createName(ctx.tfield.tid);
+ } else {
+ //ctx.tfield.tname = filterName(ctx,ctx.tfield.tname);
+ }
+ };
+
+ this.fillTEntity = function(ctx) {
+ if (ctx.tentity.tname === null) {
+ ctx.tentity.tname = ctx.tentity.tid;//
+ }
+ };
+ };
+})();
diff --git a/lib/plugin/auto/auto-tslug.js b/lib/plugin/auto/auto-tslug.js
new file mode 100644
index 0000000..f3c3562
--- /dev/null
+++ b/lib/plugin/auto/auto-tslug.js
@@ -0,0 +1,47 @@
+
+module.exports = (function () {
+
+ var filterSlug = function(ctx,value) {
+ return ctx.filterValue(value,['.',',','/','=','&','?',' '],'', function (part) {
+ return part; // todo use fully correct uri removeal and escaping.
+ })
+ };
+
+ var createTViewSlug = function(tentity) {
+ var uriViewSlash = '/';
+ var slug = uriViewSlash + tentity.tslug;
+ if (!tentity.tenable || !tentity.tparent) {
+ slug = '';
+ }
+ if (tentity.tparent) {
+ return createTViewSlug(tentity.tparent)+slug;
+ }
+ return slug;
+ };
+
+ return function AutoTSlugPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='autoTSlug';
+ ctx.description='Auto fills and checks the tslug fields.';
+ };
+
+ this.fillTField = function(ctx) {
+ if (ctx.tfield.tslug === undefined || ctx.tfield.tslug === null) {
+ ctx.tfield.tslug = ctx.tfield.tid;
+ }
+ ctx.tfield.tslug = filterSlug(ctx,ctx.tfield.tslug);
+ };
+
+ this.fillTEntity = function(ctx) {
+ if (ctx.tentity.tslug === null) {
+ ctx.tentity.tslug = ctx.tentity.tid;
+ }
+ ctx.tentity.tslug = filterSlug(ctx,ctx.tentity.tslug);
+ };
+
+ this.fillTView = function(ctx) {
+ ctx.tview.tslug = createTViewSlug(ctx.tentity).substring(1);
+ }
+ };
+})();
diff --git a/lib/plugin/auto/tentity/auto-tentity-tcode.js b/lib/plugin/auto/tentity/auto-tentity-tcode.js
new file mode 100644
index 0000000..b8455c4
--- /dev/null
+++ b/lib/plugin/auto/tentity/auto-tentity-tcode.js
@@ -0,0 +1,24 @@
+
+module.exports = (function () {
+
+ var filterCode = function(ctx,value) {
+ return ctx.filterValue(value,[' ','_','-','.',','],'',function (part) {
+ return part.toLowerCase();
+ });
+ }
+
+ return function AutoTEntityTCodePlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='autoTEntityTCode';
+ ctx.description='Fills and filters tcode to be code-safe for generating variables/functions.';
+ };
+
+ this.fillTEntity = function(ctx) {
+ if (ctx.tentity.tcode === null || ctx.tentity.tcode === null) {
+ ctx.tentity.tcode = ctx.tentity.tid;
+ }
+ ctx.tentity.tcode = filterCode(ctx,ctx.tentity.tcode);
+ };
+ };
+})();
diff --git a/lib/plugin/auto/tentity/auto-tentity-tkey.js b/lib/plugin/auto/tentity/auto-tentity-tkey.js
new file mode 100644
index 0000000..32f6c17
--- /dev/null
+++ b/lib/plugin/auto/tentity/auto-tentity-tkey.js
@@ -0,0 +1,29 @@
+
+module.exports = (function () {
+
+ var makeTKey = function(ctx) {
+ var keySlug = '';
+ for (var i = 0; i < ctx.tentity.tmeta.tmodel.tkeys.length; i++) {
+ var key = ctx.tentity.tmeta.tmodel.tkeys[i];
+ keySlug += ':'+key;
+ if (i < (ctx.tentity.tmeta.tmodel.tkeys.length - 1)) {
+ keySlug += '/';
+ }
+ }
+ return keySlug;
+ };
+
+ return function AutoTEntityTKeyPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='autoTEntityTKey';
+ ctx.description='Auto creates the tmode.tkey parameter slug.';
+ };
+
+ this.fillTEntity = function(ctx) {
+ if (ctx.tentity.tmeta.tmodel.tkey === null) {
+ ctx.tentity.tmeta.tmodel.tkey = makeTKey(ctx);
+ }
+ };
+ };
+})();
diff --git a/lib/plugin/auto/tentity/auto-tentity-tplural.js b/lib/plugin/auto/tentity/auto-tentity-tplural.js
new file mode 100644
index 0000000..3c91f6a
--- /dev/null
+++ b/lib/plugin/auto/tentity/auto-tentity-tplural.js
@@ -0,0 +1,32 @@
+
+module.exports = (function () {
+
+ var makePlural = function(name) {
+ if (name.slice(-1) === 's') {
+ return name;
+ }
+ if (name.slice(-1) === 'y') {
+ name = name.slice(0,-1) + 'ie';
+ }
+ return name + 's';
+ }
+
+ return function AutoTEntityTPluralPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='autoTEntityTPlural';
+ ctx.description='Auto create tplurals from the tname/tid field.';
+ ctx.dependencies.push('autoTName');
+ };
+
+ this.fillTEntity = function(ctx) {
+ if (ctx.tentity.tplural === null) {
+ if (ctx.tentity.tname === null) {
+ ctx.tentity.tplural = makePlural(ctx.tentity.tid);
+ } else {
+ ctx.tentity.tplural = makePlural(ctx.tentity.tname);
+ }
+ }
+ };
+ };
+})();
diff --git a/lib/plugin/auto/tview/auto-tview-ftl.js b/lib/plugin/auto/tview/auto-tview-ftl.js
new file mode 100644
index 0000000..fdcc2a8
--- /dev/null
+++ b/lib/plugin/auto/tview/auto-tview-ftl.js
@@ -0,0 +1,53 @@
+
+module.exports = (function () {
+
+ function forceLookupTFields(tview) {
+ var keys = Object.keys(tview.tmeta.tfields);
+ for (var i = 0; i < keys.length; i++) {
+ var key = keys[i];
+ var keyNew = 'FTL_' +forceLookupKeySimple() + '_' + key.substring(key.length/3,key.length/3*2); // no....its; Force template lookup
+ var tfield = tview.tmeta.tfields[key];
+ tview.tmeta.tfields[key] = undefined;
+ tview.tmeta.tfields[keyNew] = tfield;
+
+ var ckeys = Object.keys(tview);
+ for (var ii = 0; ii < ckeys.length; ii++) {
+ var ckey = ckeys[ii];
+ if (ckey === 'tmeta') {
+ continue;
+ }
+ var obj = tview[ckey];
+ if (obj && obj.tfields) {
+ var tfieldsNew = [];
+ for (var iii = 0; iii < obj.tfields.length; iii++) {
+ var tkey = obj.tfields[iii];
+ if (tkey === key) {
+ tfieldsNew.push(keyNew);
+ } else {
+ tfieldsNew.push(tkey);
+ }
+ }
+ obj.tfields = tfieldsNew;
+ }
+ }
+ }
+ }
+
+ function forceLookupKeySimple() {
+ var low = 100000;
+ var high = 999999;
+ return Math.floor(Math.random() * (high - low + 1) + low).toString(16).toUpperCase();
+ }
+
+ return function AutoTViewFTLPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='autoTViewFTL';
+ ctx.description='Automatic tview tfields key change to force table lookup.';
+ };
+
+ this.fillTView = function(ctx) {
+ forceLookupTFields(ctx.tview);
+ };
+ };
+})();
diff --git a/lib/plugin/format/format-csv.js b/lib/plugin/format/format-csv.js
new file mode 100644
index 0000000..1a05cd4
--- /dev/null
+++ b/lib/plugin/format/format-csv.js
@@ -0,0 +1,21 @@
+
+module.exports = (function () {
+
+ return function FormatCsvPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'formatCSV';
+ ctx.description = 'Export tentity list/read api in csv format.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'format-csv.json';
+ };
+
+ this.configApiTListExport = function (ctx) {
+ return ctx.renderTemplateDataList(ctx.tview.tmeta.tplugin.formatCSV.tmime);
+ };
+
+ this.configApiTReadExport = function (ctx) {
+ return ctx.renderTemplateDataRead(ctx.tview.tmeta.tplugin.formatCSV.tmime);
+ };
+ };
+})();
diff --git a/lib/plugin/format/format-csv.json b/lib/plugin/format/format-csv.json
new file mode 100644
index 0000000..8396e29
--- /dev/null
+++ b/lib/plugin/format/format-csv.json
@@ -0,0 +1,18 @@
+{
+ "masterTEntityTemplate": {
+ "tlist": { "tplugin": { "formatCSV": { "tslug": "list" }}},
+ "tread": { "tplugin": { "formatCSV": { "tslug": "read" }}},
+ "tmeta": { "tplugin": { "formatCSV": {
+ "tslug": "csv",
+ "tmime": "text/csv"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tlist": { "tplugin": { "formatCSV": { "tslug": "csv slug of api url" }}},
+ "tread": { "tplugin": { "formatCSV": { "tslug": "csv slug of api url" }}},
+ "tmeta": { "tplugin": { "formatCSV": {
+ "tslug": "The csv slug of api url.",
+ "tmime": "The csv mime type header."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/format/format-json.js b/lib/plugin/format/format-json.js
new file mode 100644
index 0000000..ce7fa26
--- /dev/null
+++ b/lib/plugin/format/format-json.js
@@ -0,0 +1,35 @@
+
+// TODO: move to default-format-json.js ??
+
+module.exports = (function () {
+
+ return function FormatJsonPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'formatJSON';
+ ctx.description = 'Export the tentity api in json format.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'format-json.json';
+ };
+
+ // flag that we support these all crud types.
+
+ this.configApiTListExport = function (ctx) {
+ };
+
+ this.configApiTCreateExport = function (ctx) {
+ };
+
+ this.configApiTReadExport = function (ctx) {
+ };
+
+ this.configApiTEditExport = function (ctx) {
+ };
+
+ this.configApiTDeleteExport = function (ctx) {
+ };
+
+ this.configApiTCountExport = function (ctx) {
+ };
+ };
+})();
diff --git a/lib/plugin/format/format-json.json b/lib/plugin/format/format-json.json
new file mode 100644
index 0000000..7f2ccdc
--- /dev/null
+++ b/lib/plugin/format/format-json.json
@@ -0,0 +1,22 @@
+{
+ "masterTEntityTemplate": {
+ "tlist": { "tplugin": { "formatJSON": { "tslug": "list" }}},
+ "tcreate": { "tplugin": { "formatJSON": { "tslug": "create" }}},
+ "tedit": { "tplugin": { "formatJSON": { "tslug": "edit" }}},
+ "tread": { "tplugin": { "formatJSON": { "tslug": "read" }}},
+ "tdelete": { "tplugin": { "formatJSON": { "tslug": "delete" }}},
+ "tcount": { "tplugin": { "formatJSON": { "tslug": "list-count" }}},
+ "tverify": { "tplugin": { "formatJSON": { "tslug": "verify" }}},
+ "tmeta": { "tplugin": { "formatJSON": { "tslug": "json" }}}
+ },
+ "masterTEntityTHelp": {
+ "tlist": { "tplugin": { "formatJSON": { "tslug": "tlist json slug part api url" }}},
+ "tcreate": { "tplugin": { "formatJSON": { "tslug": "tcreate json slug part api url" }}},
+ "tedit": { "tplugin": { "formatJSON": { "tslug": "tedit json slug part api url" }}},
+ "tread": { "tplugin": { "formatJSON": { "tslug": "tread json slug part api url" }}},
+ "delete": { "tplugin": { "formatJSON": { "tslug": "tdelete json slug part api url" }}},
+ "tcount": { "tplugin": { "formatJSON": { "tslug": "tcount json slug part api url" }}},
+ "tverify": { "tplugin": { "formatJSON": { "tslug": "tverifiy json slug part api url"}}},
+ "tmeta": { "tplugin": { "formatJSON": { "tslug": "json group part api url" }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/format/format-rss.js b/lib/plugin/format/format-rss.js
new file mode 100644
index 0000000..75abb30
--- /dev/null
+++ b/lib/plugin/format/format-rss.js
@@ -0,0 +1,13 @@
+
+module.exports = (function () {
+
+ return function FormatRssPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'formatRSS';
+ ctx.description = 'Export tentity list/read api in rss format.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'format-rss.json';
+ };
+ };
+})();
diff --git a/lib/plugin/format/format-rss.json b/lib/plugin/format/format-rss.json
new file mode 100644
index 0000000..f20e946
--- /dev/null
+++ b/lib/plugin/format/format-rss.json
@@ -0,0 +1,17 @@
+{
+ "masterTEntityTemplate": {
+ "tlist": { "tplugin": { "formatRSS": { "tslug": "list" }}},
+ "tmeta": { "tplugin": { "formatRSS": {
+ "tslug": "rss",
+ "itemTitle": "$data._id",
+ "itemLink": "$siteLink/ui/$modelUri/read/$[\"data._id\"]",
+ "itemDate": "",
+ "itemDescription": "",
+ "itemFields": ""
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tlist": { "tplugin": { "formatRSS": { "tslug": "rss slug of api url" }}},
+ "tmeta": { "tplugin": { "formatRSS": { "tslug": "rss slug of api url" }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/format/format-xml.js b/lib/plugin/format/format-xml.js
new file mode 100644
index 0000000..cfdc302
--- /dev/null
+++ b/lib/plugin/format/format-xml.js
@@ -0,0 +1,75 @@
+var xmlmapping = require('xml-mapping');
+var rawbody = require('raw-body');
+var typeis = require('type-is');
+
+module.exports = (function () {
+
+ function bodyParserXml() {
+ return function(req, res, next) {
+ if (req._body) return next();
+ req.body = req.body || {};
+
+ if (!typeis(req, 'xml')) return next();
+
+ // flag as parsed
+ req._body = true;
+
+ // parse
+ rawbody(req, {
+ limit: options.limit || '100kb',
+ length: req.headers['content-length'],
+ encoding: 'utf8'
+ }, function (err, buf) {
+ if (err) return next(err);
+
+ if (0 == buf.length) {
+ return next(error(400, 'invalid xml, empty body'));
+ }
+ try {
+ req.body = xmlmapping.dump(buf/*, options.reviver*/);
+ } catch (err){
+ err.body = buf;
+ err.status = 400;
+ return next(err);
+ }
+ next();
+ })
+ }
+ }
+
+ return function FormatXmlPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'formatXML';
+ ctx.description = 'Export the tentity api in xml format.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'format-xml.json';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.server.use(bodyParserXml());
+ }
+
+ // flag that we support these all crud types.
+
+ this.configApiTListExport = function (ctx) {
+ return ctx.renderTemplateDataList(ctx.tview.tmeta.tplugin.formatXML.tmime);
+ };
+
+ this.configApiTCreateExport = function (ctx) {
+ };
+
+ this.configApiTReadExport = function (ctx) {
+ return ctx.renderTemplateDataRead(ctx.tview.tmeta.tplugin.formatXML.tmime);
+ };
+
+ this.configApiTEditExport = function (ctx) {
+ };
+
+ this.configApiTDeleteExport = function (ctx) {
+ };
+
+ this.configApiTCountExport = function (ctx) {
+ };
+ };
+})();
diff --git a/lib/plugin/format/format-xml.json b/lib/plugin/format/format-xml.json
new file mode 100644
index 0000000..b028f91
--- /dev/null
+++ b/lib/plugin/format/format-xml.json
@@ -0,0 +1,28 @@
+{
+ "masterTEntityTemplate": {
+ "tlist": { "tplugin": { "formatXML": { "tslug": "list" }}},
+ "tcreate": { "tplugin": { "formatXML": { "tslug": "create" }}},
+ "tedit": { "tplugin": { "formatXML": { "tslug": "edit" }}},
+ "tread": { "tplugin": { "formatXML": { "tslug": "read" }}},
+ "tdelete": { "tplugin": { "formatXML": { "tslug": "delete" }}},
+ "tcount": { "tplugin": { "formatXML": { "tslug": "list-count" }}},
+ "tverify": { "tplugin": { "formatXML": { "tslug": "verify" }}},
+ "tmeta": { "tplugin": { "formatXML": {
+ "tslug": "xml",
+ "tmime": "text/xml"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tlist": { "tplugin": { "formatXML": { "tslug": "xml slug of api url" }}},
+ "tcreate": { "tplugin": { "formatXML": { "tslug": "xml slug of api url" }}},
+ "tedit": { "tplugin": { "formatXML": { "tslug": "xml slug of api url" }}},
+ "tread": { "tplugin": { "formatXML": { "tslug": "xml slug of api url" }}},
+ "tdelete": { "tplugin": { "formatXML": { "tslug": "xml slug of api url" }}},
+ "tcount": { "tplugin": { "formatXML": { "tslug": "xml slug of api url" }}},
+ "tverify": { "tplugin": { "formatXML": { "tslug": "xml slug of api url" }}},
+ "tmeta": { "tplugin": { "formatXML": {
+ "tslug": "The xml slug of api url.",
+ "tmime": "The xml mime type header."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/server/config/config-resources-mobile.js b/lib/plugin/server/config/config-resources-mobile.js
new file mode 100644
index 0000000..841510d
--- /dev/null
+++ b/lib/plugin/server/config/config-resources-mobile.js
@@ -0,0 +1,63 @@
+var debug = require('debug')('ff:tcrud:plugin:server:config:resouces:mobile');
+var configRegistry = require('./../../../config-registry');
+var configUtil = require('./../../../config-util');
+var fetch = require('fetch');
+
+module.exports = (function () {
+
+ var webAssets = [];
+
+ var fetchHashResource = function(fetchEntry,cb) {
+ fetch.fetchUrl(fetchEntry.url,function(err, meta, data) {
+ debug('fetched url: '+fetchEntry.url+' length: '+meta.responseHeaders['content-length']);
+ var assetHash = configUtil.stringHash(''+data);
+ webAssets.push({
+ url: fetchEntry.url,
+ type: fetchEntry.type,
+ hash: assetHash
+ });
+ cb();
+ });
+ };
+
+ var fetchHashResources = function(fetchList, cb) {
+ var startTime = new Date().getTime();
+ debug('createHashResources: '+fetchList.length);
+ var resourceStack = fetchList;
+ var resourceLoader = function() {
+ resourceStack = resourceStack.slice(1);
+ if (resourceStack.length === 0) {
+ debug('fetchHashResources done in '+(new Date().getTime()-startTime)+' ms.');
+ cb();
+ } else {
+ fetchHashResource(resourceStack[0],resourceLoader);
+ }
+ };
+ fetchHashResource(resourceStack[0],resourceLoader);
+ };
+
+ return function ServerConfigResourcesMobilePlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'serverConfigResourcesMobile';
+ ctx.description = 'Exports client resources mobile.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'config-resources-mobile.json';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.server.get(ctx.createSlugApiServerBase(),ctx.renderFunctionJSON(function () {
+ return {
+ resources: webAssets
+ }
+ }));
+ };
+
+ this.configPostBoot = function(ctx) {
+ fetchHashResources(configRegistry.createClientResourceFetchList(), function() {
+ debug('Total assets build: '+webAssets.length);
+ });
+ }
+ };
+})();
+
diff --git a/lib/plugin/server/config/config-resources-mobile.json b/lib/plugin/server/config/config-resources-mobile.json
new file mode 100644
index 0000000..ec153ac
--- /dev/null
+++ b/lib/plugin/server/config/config-resources-mobile.json
@@ -0,0 +1,12 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "serverConfigResourcesMobile": {
+ "tslug": "config/client-resources-mobile"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "serverConfigResourcesMobile": {
+ "tslug": "The client resource for mobile slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/server/config/config-resources-web.js b/lib/plugin/server/config/config-resources-web.js
new file mode 100644
index 0000000..c896378
--- /dev/null
+++ b/lib/plugin/server/config/config-resources-web.js
@@ -0,0 +1,144 @@
+var debug = require('debug')('ff:tcrud:plugin:server:config:resouces:web');
+var configRegistry = require('./../../../config-registry');
+var configUtil = require('./../../../config-util');
+var fetch = require('fetch');
+
+// FIXME: use type as key
+var assetCss = '';
+var assetCssUrl = '';
+var assetCssHash = '';
+var assetCssData = '';
+var assetCssDataUrl = '';
+var assetCssDataHash = '';
+var assetJs = '';
+var assetJsUrl = '';
+var assetJsHash = '';
+
+var plugin = (function () {
+
+ var webAssets = [];
+
+ var fetchResource = function(fetchEntry,cb) {
+ fetch.fetchUrl(fetchEntry.url,function(err, meta, data) {
+ if (err) {
+ throw err;
+ }
+ debug('fetched url: '+fetchEntry.url+' length: '+meta.responseHeaders['content-length']);
+ //debug('meta: '+JSON.stringify(meta));
+
+ if (fetchEntry.type === 'css') {
+ assetCss += '\n/* FILE: '+fetchEntry.url+' */\n';
+ assetCss += data;
+ } else if (fetchEntry.type === 'js') {
+ assetJs += '\n/* FILE: '+fetchEntry.url+' */\n';
+ assetJs += data;
+ } else if (fetchEntry.type === 'dss') {
+ assetCssData += '\n/* FILE: '+fetchEntry.url+' */\n';
+ assetCssData += data;
+ }
+ cb();
+ });
+ };
+
+ var fetchResources = function(fetchList, cb) {
+ var startTime = new Date().getTime();
+ debug('createResources: '+fetchList.length);
+ var resourceStack = fetchList;
+ var resourceLoader = function() {
+ resourceStack = resourceStack.slice(1);
+ if (resourceStack.length === 0) {
+ debug('fetchResources done in '+(new Date().getTime()-startTime)+' ms.');
+ cb();
+ } else {
+ fetchResource(resourceStack[0],resourceLoader);
+ }
+ };
+ fetchResource(resourceStack[0],resourceLoader);
+ };
+
+ return function ServerConfigResourcesWebPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'serverConfigResourcesWeb';
+ ctx.description = 'Exports client resources web.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'config-resources-web.json';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.server.get(ctx.createSlugApiServerBase(),ctx.renderFunctionJSON(function () {
+ return {
+ resources: webAssets
+ }
+ }));
+ };
+
+ this.configPostBoot = function(ctx) {
+ fetchResources(configRegistry.createClientResourceFetchList(), function() {
+ assetJsHash = configUtil.stringHash(assetJs);
+ assetCssHash = configUtil.stringHash(assetCss);
+ assetCssDataHash = configUtil.stringHash(assetCssData);
+ debug('Total size assets.js: '+assetJs.length+' hash: '+assetJsHash);
+ debug('Total size assets.css: '+assetCss.length+' hash: '+assetCssHash);
+ debug('Total size assets-data.css: '+assetCssData.length+' hash: '+assetCssDataHash);
+ // note: js first to inject href asap by loader
+ webAssets.push({
+ url: assetJsUrl,
+ type: 'js',
+ hash: assetJsHash
+ });
+ webAssets.push({
+ url: assetCssUrl,
+ type: 'css',
+ hash: assetCssHash
+ });
+ webAssets.push({
+ url: assetCssDataUrl,
+ type: 'dss',
+ hash: assetCssDataHash
+ });
+ });
+ }
+ };
+})();
+
+var pluginDownload = (function () {
+
+ return function ServerConfigResourcesWebAssetPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'serverConfigResourcesWebAsset';
+ ctx.description = 'Exports downloaded client web resources.';
+ ctx.dependencies.push('serverConfigResourcesWeb');
+ };
+
+ this.configServer = function(ctx) {
+
+ assetCssDataUrl = ctx.createSlugApiPluginBase('assets-data.css');
+ ctx.server.get(assetCssDataUrl,function (req, res, next) {
+ res.set('Content-Type','text/css');
+ res.write(assetCssData);
+ res.end();
+ });
+
+ assetCssUrl = ctx.createSlugApiPluginBase('assets.css');
+ ctx.server.get(assetCssUrl,function (req, res, next) {
+ res.set('Content-Type','text/css');
+ res.write(assetCss);
+ res.end();
+ });
+
+ assetJsUrl = ctx.createSlugApiPluginBase('assets.js');
+ ctx.server.get(assetJsUrl,function (req, res, next) {
+ res.set('Content-Type','application/javascript');
+ res.write(assetJs);
+ res.end();
+ });
+ };
+ };
+})();
+
+module.exports = {
+ web: plugin,
+ webAssets: pluginDownload
+};
diff --git a/lib/plugin/server/config/config-resources-web.json b/lib/plugin/server/config/config-resources-web.json
new file mode 100644
index 0000000..9d4d74b
--- /dev/null
+++ b/lib/plugin/server/config/config-resources-web.json
@@ -0,0 +1,12 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "serverConfigResourcesWeb": {
+ "tslug": "config/client-resources-web"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "serverConfigResourcesWeb": {
+ "tslug": "The client resources web slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/server/config/config-routes.js b/lib/plugin/server/config/config-routes.js
new file mode 100644
index 0000000..dd1280f
--- /dev/null
+++ b/lib/plugin/server/config/config-routes.js
@@ -0,0 +1,68 @@
+var buildServerExpress = require('./../../../build-server-express');
+
+module.exports = (function () {
+
+ var renderServerRoutes = function (server) {
+ if (!server) {
+ throw new Error('no server');
+ }
+ return function (req, res, next) {
+ var routeList = buildServerExpress.getRoutes(server);
+ if (!routeList) {
+ routeList = [];
+ }
+ var reqGroups = req.query.groups;
+ var groupList = [];
+ if (reqGroups) {
+ groupList = reqGroups.split(',');
+ }
+ var result = {
+ all: [],
+ };
+ for (var i = 0; i < groupList.length; i++) {
+ var groupName = groupList[i].split('/').join('_');
+ result[groupName] = [];
+ }
+ groupList.sort(function(a, b) {
+ return a.length < b.length; // longest first as we break on first hit
+ });
+
+ for (i = 0; i < routeList.length; i++) {
+ var route = routeList[i];
+ var added = false;
+ for (var ii = 0; ii < groupList.length; ii++) {
+ if (route.uriPath.indexOf(groupList[ii]) > 0) {
+ var groupName = groupList[ii].split('/').join('_');
+ result[groupName].push(route);
+ added = true;
+ break;
+ }
+ }
+ if (!added) {
+ result.all.push(route);
+ }
+ }
+ res.json({
+ data: result
+ });
+ };
+ }
+
+ return function ServerConfigRoutesPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'serverConfigRoutes';
+ ctx.description = 'Exports all server routes.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'config-routes.json';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.server.get(ctx.createSlugApiServerBase(),renderServerRoutes(ctx.server));
+ };
+
+ this.configPostBoot = function(ctx) {
+ buildServerExpress.saveRoutes(ctx.server);
+ };
+ };
+})();
diff --git a/lib/plugin/server/config/config-routes.json b/lib/plugin/server/config/config-routes.json
new file mode 100644
index 0000000..a3d61e0
--- /dev/null
+++ b/lib/plugin/server/config/config-routes.json
@@ -0,0 +1,12 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "serverConfigRoutes": {
+ "tslug": "config/routes"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "serverConfigRoutes": {
+ "tslug": "The routes slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/server/config/config-tmenu.js b/lib/plugin/server/config/config-tmenu.js
new file mode 100644
index 0000000..43dfc43
--- /dev/null
+++ b/lib/plugin/server/config/config-tmenu.js
@@ -0,0 +1,28 @@
+var configRegistry = require('./../../../config-registry');
+
+module.exports = (function () {
+
+ return function ServerConfigTMenuPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'serverConfigTMenu';
+ ctx.description = 'Exports tmenu.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'config-tmenu.json';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.server.get(ctx.createSlugApiServerBase(),ctx.renderFunctionJSON(function() {
+ var result = {};
+ for (var key in configRegistry.getMasterConfig().rootTMenu) {
+ var value = configRegistry.getMasterConfig().rootTMenu[key];
+ if (value.items.length > 0) {
+ result[key] = value; // remove empty menus
+ }
+ }
+ return result;
+ }));
+ };
+ };
+})();
+
diff --git a/lib/plugin/server/config/config-tmenu.json b/lib/plugin/server/config/config-tmenu.json
new file mode 100644
index 0000000..dbe8fbb
--- /dev/null
+++ b/lib/plugin/server/config/config-tmenu.json
@@ -0,0 +1,12 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "serverConfigTMenu": {
+ "tslug": "config/menu"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "serverConfigTMenu": {
+ "tslug": "The config tmenu slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/server/config/config-tview.js b/lib/plugin/server/config/config-tview.js
new file mode 100644
index 0000000..bd891f7
--- /dev/null
+++ b/lib/plugin/server/config/config-tview.js
@@ -0,0 +1,23 @@
+var configRegistry = require('./../../../config-registry');
+
+module.exports = (function () {
+
+ return function ServerConfigTViewPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'serverConfigTView';
+ ctx.description = 'Exports tentity tviews.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'config-tview.json';
+ };
+
+ this.configApi = function(ctx) {
+ ctx.server.get(ctx.createSlugApiTEntityBase(),ctx.renderFunctionJSON(function () {
+ return {
+ tview: ctx.tview
+ }
+ }));
+ };
+ };
+})();
+
diff --git a/lib/plugin/server/config/config-tview.json b/lib/plugin/server/config/config-tview.json
new file mode 100644
index 0000000..d5e5f01
--- /dev/null
+++ b/lib/plugin/server/config/config-tview.json
@@ -0,0 +1,12 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "serverConfigTView": {
+ "tslug": "config/tview"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "serverConfigTView": {
+ "tslug": "The config tview slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/server/debug.js b/lib/plugin/server/debug.js
new file mode 100644
index 0000000..825f111
--- /dev/null
+++ b/lib/plugin/server/debug.js
@@ -0,0 +1,42 @@
+var configRegistry = require('./../../config-registry');
+
+module.exports = (function () {
+
+ var masterDebug = function(ctx,modelName,modelType,model) {
+ ctx.server.get(
+ ctx.createSlugApiServerBase('master')+
+ '/'+modelName+
+ '/'+modelType
+ ,ctx.renderFunctionJSON(function() {
+ var result = {};
+ result[modelName] = model;
+ return result;
+ })
+ );
+ };
+
+ return function ServerDebugPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'serverDebug';
+ ctx.description = 'Exports some debug data.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'debug.json';
+ };
+
+ this.configServer = function(ctx) {
+ var mt = configRegistry.getMasterTemplates();
+ masterDebug(ctx,'tfield', 'template', mt.masterTFieldTemplate);
+ masterDebug(ctx,'tfield', 'thelp', mt.masterTFieldTHelp);
+ masterDebug(ctx,'tentity', 'template', mt.masterTEntityTemplate);
+ masterDebug(ctx,'tentity', 'thelp', mt.masterTEntityTHelp);
+ masterDebug(ctx,'tview', 'template', mt.masterTViewTemplate);
+ masterDebug(ctx,'tview', 'thelp', mt.masterTViewTHelp);
+
+ // todo: TypeError: Converting circular structure to JSON
+ //ctx.server.get(ctx.createSlugApiServerBase('config/root-tentity'),this.renderFunction(function() {
+ // return configRegistry.rootTEntity;
+ //}));
+ };
+ };
+})();
diff --git a/lib/plugin/server/debug.json b/lib/plugin/server/debug.json
new file mode 100644
index 0000000..d2529b5
--- /dev/null
+++ b/lib/plugin/server/debug.json
@@ -0,0 +1,12 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "serverDebug": {
+ "tslug": "debug"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "serverDebug": {
+ "tslug": "The server debug slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/server/info/info-plugins.js b/lib/plugin/server/info/info-plugins.js
new file mode 100644
index 0000000..e23e90c
--- /dev/null
+++ b/lib/plugin/server/info/info-plugins.js
@@ -0,0 +1,55 @@
+var configRegistry = require('./../../../config-registry');
+
+module.exports = (function () {
+
+ var createPluginInfo = function() {
+ var result = {};
+ for (var pluginIdx in configRegistry.getMasterConfig().plugins) {
+ var plugin = configRegistry.getMasterConfig().plugins[pluginIdx];
+ var pluginExtensions = Object.keys(plugin).slice(0,-1);
+ //TODO: pluginExtensions.remove('tmeta');
+ var pluginData = {
+ extensions: pluginExtensions,
+ tmeta: plugin.tmeta,
+ troutes: plugin.troutes
+ };
+ result[plugin.tmeta.key] = pluginData;
+ }
+ return result;
+ };
+
+ var createPluginKeyList = function() {
+ var result = [];
+ for (var pluginIdx in configRegistry.getMasterConfig().plugins) {
+ var plugin = configRegistry.getMasterConfig().plugins[pluginIdx];
+ result.push(plugin.tmeta.key);
+ }
+ return result;
+ };
+
+ return function ServerInfoPluginsPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='serverInfoPlugins';
+ ctx.description='Makes the server plugins info public as json';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'info-plugins.json';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.server.get(ctx.createSlugApiServerBase(),ctx.renderFunctionJSON(function () {
+ return {
+ plugins: {
+ keys: {
+ registrated: createPluginKeyList(),
+ backends: Object.keys(configRegistry.getMasterConfig().backends),
+ validators: Object.keys(configRegistry.getMasterConfig().validators)
+ },
+ info: createPluginInfo()
+ }
+ }
+ }));
+ };
+ };
+})();
+
diff --git a/lib/plugin/server/info/info-plugins.json b/lib/plugin/server/info/info-plugins.json
new file mode 100644
index 0000000..4509c2d
--- /dev/null
+++ b/lib/plugin/server/info/info-plugins.json
@@ -0,0 +1,12 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "serverInfoPlugins": {
+ "tslug": "info/plugins"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "serverInfoPlugins": {
+ "tslug": "The info plugins slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/server/info/info-uptime.js b/lib/plugin/server/info/info-uptime.js
new file mode 100644
index 0000000..28b964c
--- /dev/null
+++ b/lib/plugin/server/info/info-uptime.js
@@ -0,0 +1,28 @@
+
+module.exports = (function () {
+
+ var timeBoot = new Date().getTime();
+
+ return function ServerInfoUptimePlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='serverInfoUptime';
+ ctx.description='Makes the server uptime info public as json';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'info-uptime.json';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.server.get(ctx.createSlugApiServerBase(),ctx.renderFunctionJSON(function () {
+ var timeNow = new Date().getTime();
+ return {
+ serverInfoUptime: {
+ time_boot: timeBoot,
+ time_now: timeNow,
+ uptime: timeNow-timeBoot
+ }
+ }
+ }));
+ };
+ };
+})();
diff --git a/lib/plugin/server/info/info-uptime.json b/lib/plugin/server/info/info-uptime.json
new file mode 100644
index 0000000..36bcb1b
--- /dev/null
+++ b/lib/plugin/server/info/info-uptime.json
@@ -0,0 +1,12 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "serverInfoUptime": {
+ "tslug": "info/uptime"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "serverInfoUptime": {
+ "tslug": "The info uptime slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/ui/angular/server/ui-angular-server-plugins.js b/lib/plugin/ui/angular/server/ui-angular-server-plugins.js
new file mode 100644
index 0000000..ea1e040
--- /dev/null
+++ b/lib/plugin/ui/angular/server/ui-angular-server-plugins.js
@@ -0,0 +1,25 @@
+
+module.exports = (function () {
+
+ return function UIAngularServerRoutesPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'uiAngularServerPlugins';
+ ctx.description = 'Adds an angular server plugins page.';
+ ctx.dependencies.push('angular');
+ };
+
+ this.configServer = function(ctx) {
+ ctx.hostTemplateJS('js/server-plugins');
+ ctx.hostTemplateHTML('thtml/plugins',true);
+
+ ctx.registrateMenuItem({
+ name: 'Server Plugins',
+ link: ctx.troot.tmeta.tplugin.angular.tbase+'/server/plugins',
+ enable: true,
+ roles: [],
+ icon: 'fa fa-umbrella'
+ },'server');
+ };
+ };
+})();
diff --git a/lib/plugin/ui/angular/server/ui-angular-server-routes.js b/lib/plugin/ui/angular/server/ui-angular-server-routes.js
new file mode 100644
index 0000000..581a0a7
--- /dev/null
+++ b/lib/plugin/ui/angular/server/ui-angular-server-routes.js
@@ -0,0 +1,26 @@
+
+module.exports = (function () {
+
+ return function UIAngularServerRoutesPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'uiAngularServerRoutes';
+ ctx.description = 'Adds an angular server routes page.';
+ ctx.dependencies.push('angular');
+ ctx.dependencies.push('serverInfoPlugins');
+ };
+
+ this.configServer = function(ctx) {
+ ctx.hostTemplateJS('js/server-routes');
+ ctx.hostTemplateHTML('thtml/routes',true);
+
+ ctx.registrateMenuItem({
+ name: 'Server Routes',
+ link: ctx.troot.tmeta.tplugin.angular.tbase+'/server/routes',
+ enable: true,
+ roles: [],
+ icon: 'fa fa-road'
+ },'server');
+ };
+ };
+})();
diff --git a/lib/plugin/ui/angular/ui-angular.js b/lib/plugin/ui/angular/ui-angular.js
new file mode 100644
index 0000000..84dda48
--- /dev/null
+++ b/lib/plugin/ui/angular/ui-angular.js
@@ -0,0 +1,128 @@
+var debug = require('debug')('ff:tcrud:ui:angular:boot');
+
+module.exports = (function () {
+
+ var renderCrudController = function (tview, thtmlPrefix, tapiPrefix, tapiPrefix2) {
+
+ if (tview.tmeta.tmodel.tkeys.length === 0) {
+ throw Error('no model keys in: '+tview.tid);
+ }
+
+ var keySlug = '';
+ for (var i = 0; i < tview.tmeta.tmodel.tkeys.length; i++) {
+ var key = tview.tmeta.tmodel.tkeys[i];
+ keySlug += '$routeParams.'+key;
+ if (i < (tview.tmeta.tmodel.tkeys.length - 1)) {
+ keySlug += '/';
+ }
+ }
+
+ var tviewCode = '';
+ var slugParts = tview.tslug.split('/');
+ for (var i = 0; i < slugParts.length; i++) {
+ var part = slugParts[i];
+ part = part.replace(/-/g,''); // TODO use tcode which is already cleaned ?
+ tviewCode += part.substring(0,1).toUpperCase()+part.substring(1);
+ }
+ //debug('tviewCode: $s',tviewCode);
+
+ return function (req, res, next) {
+ res.set('Content-Type', 'text/javascript');
+ res.render('node-ff-tcrud/angular/js/crud/controller',{
+ tview: tview,
+ tviewCode: tviewCode,
+ thtmlPrefix: thtmlPrefix,
+ tapiPrefix: tapiPrefix,
+ tapiPrefix2: tapiPrefix2,
+ ejsRouteParams: keySlug
+ });
+ };
+ }
+
+ var renderCrudTemplate = function (tview,paction) {
+
+ var ejsKeyRow = '';
+ var ejsKeyData = '';
+ for (var i = 0; i < tview.tmeta.tmodel.tkeys.length; i++) {
+ var key = tview.tmeta.tmodel.tkeys[i];
+ ejsKeyRow += '{{row.'+key+'}}';
+ ejsKeyData += '{{data.'+key+'}}';
+ if (i < (tview.tmeta.tmodel.tkeys.length - 1)) {
+ ejsKeyRow += '/';
+ ejsKeyData += '/';
+ }
+ }
+
+ return function (req, res, next) {
+ res.render('node-ff-tcrud/angular/'+paction+'/'+req.params.action,{
+ tview: tview,
+ ejsKeyRow: ejsKeyRow,
+ ejsKeyData: ejsKeyData
+ });
+ };
+ }
+
+ return function UIAngularCorePlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key = 'angular';
+ ctx.description = 'Exports angular ui data.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'ui-angular.json';
+ ctx.dependencies.push('uiLibAngular');
+ ctx.dependencies.push('uiLibFFSpaLoader');
+ //ctx.dependencies.push('uiLibTopcoat');
+ ctx.dependencies.push('serverConfigTMenu');
+ ctx.dependencies.push('uiLibFontFaceOnLoad');
+
+ };
+
+ this.configServer = function(ctx) {
+ ctx.hostTemplateJS('js/application');
+ ctx.hostTemplateJS('js/application-font');
+ ctx.hostTemplateJS('js/application-controller')
+ ctx.hostTemplateJS('js/navigation-service');
+
+ ctx.hostTemplateHTML('thtml/application-view',true);;
+ ctx.hostTemplateHTML('thtml/application-top',true);
+ ctx.hostTemplateHTML('thtml/application-top-action',true);
+ ctx.hostTemplateHTML('thtml/application-top-tabs',true);
+
+ var uiPath = ctx.troot.tmeta.tplugin.angular.tbase;
+ debug('Exported uiPath: %s',uiPath);
+
+ ctx.server.get('/', ctx.renderRedirect(uiPath));
+ ctx.server.get(uiPath, ctx.renderTemplate('index','text/html'));
+ ctx.server.get(uiPath+'/*', ctx.renderTemplate('index','text/html')); // must be last; for HTML5 history
+
+ ctx.registrateMenu({
+ name: 'Server',
+ icon: 'fa fa-server'
+ },'server'); // move ?
+ };
+
+ this.configApi = function(ctx) {
+ var uriPrefix = ctx.createSlugApiTEntityBase();
+ var thtmlPrefix = uriPrefix + '/' + ctx.tview.tmeta.tplugin.angular.thtml;
+ var tapiPrefix = ctx.createSlugApiTEntityBasePlugin('formatJSON'); // TODO: move to tview ?
+ var tapiPrefix2 = ctx.createSlugApiTEntityBasePlugin('serverConfigTView'); // TODO: move to tview ?
+
+
+ ctx.server.get(uriPrefix + '/thtml/crud/:action', renderCrudTemplate(ctx.tview,'thtml/crud/'));
+ ctx.server.get(uriPrefix + '/crud/controller.js', renderCrudController(ctx.tview,thtmlPrefix,tapiPrefix,tapiPrefix2));
+ //ctx.server.get(uriPrefix + '/service.js', renderCrudService(tview))
+
+ ctx.registrateClientJSResource(uriPrefix + '/crud/controller.js');
+
+ if (ctx.tview.tmeta.tmenu.tenable && ctx.tview.tmeta.tmenu.tkey !== null && ctx.tview.tmeta.tmenu.titem) {
+ ctx.registrateMenuItem({
+ name: ctx.tview.tmeta.tmenu.tname,
+ link: ctx.tview.tmeta.tplugin.angular.tbase+'/'+ctx.tview.tslug+'/'+ctx.tview.tlist.tplugin.angular.tslug,
+ enable: ctx.tview.tlist.tenable,
+ roles: ctx.tview.tlist.troles,
+ icon: ctx.tview.tmeta.tmenu.ticon
+ },ctx.tview.tmeta.tmenu.tkey);
+ }
+ };
+ };
+})();
diff --git a/lib/plugin/ui/angular/ui-angular.json b/lib/plugin/ui/angular/ui-angular.json
new file mode 100644
index 0000000..ee57747
--- /dev/null
+++ b/lib/plugin/ui/angular/ui-angular.json
@@ -0,0 +1,72 @@
+{
+ "masterTEntityTemplate": {
+ "tlist": { "tplugin": { "angular": {
+ "tslug": "list",
+ "thtml": "crud/list",
+ "tcontroller": {
+ "prefix": "tcrudAuto",
+ "postfix": "ListCntr",
+ "argu": "$scope, $http, $location, $routeParams, navigationService"
+ },
+ "tlinks": {
+ "DataTODO":"/ui/XNodeData/list/XNode/{{row.net_id}}/{{row.net_id}}"
+ }
+ }}},
+ "tcreate": { "tplugin": { "angular": {
+ "tslug": "create",
+ "thtml": "crud/create",
+ "tcontroller": {
+ "prefix": "tcrudAuto",
+ "postfix": "CreateCntr",
+ "argu": "$scope, $http, $location, $routeParams, navigationService"
+ }
+ }}},
+ "tedit": { "tplugin": { "angular": {
+ "tslug": "edit",
+ "thtml": "crud/edit",
+ "tcontroller": {
+ "prefix": "tcrudAuto",
+ "postfix": "EditCntr",
+ "argu": "$scope, $http, $location, $routeParams, navigationService"
+ }
+ }}},
+ "tread": { "tplugin": { "angular": {
+ "tslug": "read",
+ "thtml": "crud/read",
+ "troute": {
+
+ },
+ "tcontroller": {
+ "prefix": "tcrudAuto",
+ "postfix": "ReadCntr",
+ "argu": "$scope, $http, $location, $routeParams, navigationService"
+ }
+ }}},
+ "tdelete": { "tplugin": { "angular": {
+ "tslug": "delete",
+ "thtml": "crud/delete",
+ "tcontroller": {
+ "prefix": "tcrudAuto",
+ "postfix": "DeleteCntr",
+ "argu": "$scope, $http, $location, $routeParams, navigationService"
+ }
+ }}},
+ "tcount": { "tplugin": { "angular": { "tslug": "list-count" }}},
+ "tverify": { "tplugin": { "angular": { "tslug": "verify" }}},
+ "tmeta": { "tplugin": { "angular": {
+ "tslug": "angular",
+ "tbase": "/ui",
+ "thtml": "thtml"
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tlist": { "tplugin": { "angular": { "tslug": "slug of api url" }}},
+ "tcreate": { "tplugin": { "angular": { "tslug": "slug of api url" }}},
+ "tedit": { "tplugin": { "angular": { "tslug": "slug of api url" }}},
+ "tread": { "tplugin": { "angular": { "tslug": "slug of api url" }}},
+ "tdelete": { "tplugin": { "angular": { "tslug": "slug of api url" }}},
+ "tcount": { "tplugin": { "angular": { "tslug": "slug of api url" }}},
+ "tverify": { "tplugin": { "angular": { "tslug": "slug of api url" }}},
+ "tmeta": { "tplugin": { "angular": { "tslug": "slug of api url" }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/ui/lib/ui-lib-angular.js b/lib/plugin/ui/lib/ui-lib-angular.js
new file mode 100644
index 0000000..a5c2a01
--- /dev/null
+++ b/lib/plugin/ui/lib/ui-lib-angular.js
@@ -0,0 +1,46 @@
+
+module.exports = (function () {
+
+ var incHostFileJSNodeModule = function(ctx,includeFile,namePart) {
+ if (includeFile) {
+ ctx.hostFileJSNodeModule({file: 'angular-'+namePart+'.min.js', path: 'angular-'+namePart});
+ }
+ };
+
+ return function UILibAngularPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiLibAngular';
+ ctx.description='Adds angular libs to resources.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'ui-lib-angular.json';
+ ctx.dependencies.push('uiLibJQuery');
+
+ };
+
+ this.configServer = function(ctx) {
+ ctx.hostFileJSNodeModule({file: 'angular.min.js', path: 'angular'});
+
+ var inc = ctx.troot.tmeta.tplugin.uiLibAngular.include;
+ incHostFileJSNodeModule(ctx,inc.animate,'animate');
+ incHostFileJSNodeModule(ctx,inc.resource,'resource');
+ incHostFileJSNodeModule(ctx,inc.route,'route');
+ incHostFileJSNodeModule(ctx,inc.touch,'touch');
+
+ if (inc['ui-grid']) {
+ var filter = {
+ '(^.*url.*?;)': '', // removed all urls
+ '(@font-face[\\s\\S]*?})': '', // remove font-face
+ '(^\\s+)': '', // clean up white space
+ '(\\{[\\s\\/]+\\/.*)': '{', // rm comment on functions
+ '(^|\\s\\/\\/.*)': '', // rm comment lines
+ '(\\/\\*[\\s\\*\\!][\\s\\S]*?\\*\\/)': '', // rm comment blocks
+ };
+ ctx.hostFileJSNodeModule({file: 'ui-grid.min.js', path: 'angular-ui-grid'});
+ ctx.hostFileCSSNodeModule({file: 'ui-grid.css', path: 'angular-ui-grid', filterRegex: filter});
+
+ ctx.hostFileCSSFontNodeModule({file: 'ui-grid.ttf', path: 'angular-ui-grid/', fontFamily: 'ui-grid'});
+ }
+ };
+ };
+})();
diff --git a/lib/plugin/ui/lib/ui-lib-angular.json b/lib/plugin/ui/lib/ui-lib-angular.json
new file mode 100644
index 0000000..0ad9fc1
--- /dev/null
+++ b/lib/plugin/ui/lib/ui-lib-angular.json
@@ -0,0 +1,19 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "uiLibAngular": {
+ "tslug": "uiLibAngular",
+ "include": {
+ "animate": true,
+ "resource": true,
+ "route": true,
+ "touch": true,
+ "ui-grid": true
+ }
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "uiLibAngular": {
+ "tslug": "The angular lib slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/ui/lib/ui-lib-bootswatch.js b/lib/plugin/ui/lib/ui-lib-bootswatch.js
new file mode 100644
index 0000000..adc9bc8
--- /dev/null
+++ b/lib/plugin/ui/lib/ui-lib-bootswatch.js
@@ -0,0 +1,27 @@
+
+module.exports = (function () {
+
+ return function UILibBootstrapPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiLibBootswatch';
+ ctx.description='Adds and hosts bootstrap.css as client resource.';
+ };
+
+ this.configServer = function(ctx) {
+ var filter = {
+ '(^.*url.*?;)': '', // removed all urls
+ '(@font-face[\\s\\S]*?})': '', // remove font-face
+ //'(body[\\s\\S]*?})': '', // remove body
+ '(^\\s+)': '', // clean up white space
+ '(\\{[\\s\\/]+\\/.*)': '{', // rm comment on functions
+ '(^|\\s\\/\\/.*)': '', // rm comment lines
+ '(\\/\\*[\\s\\*\\!][\\s\\S]*?\\*\\/)': '', // rm comment blocks
+ };
+ ctx.hostFileCSSNodeModule({file: 'bootstrap.css', path: 'bootswatch/paper', filterRegex: filter});
+ ctx.hostFileJSNodeModule({file: 'bootstrap.js', path: 'bootstrap/dist/js'});
+
+ ctx.hostFileCSSFontNodeModule({file: 'glyphicons-halflings-regular.ttf', path: 'bootswatch/fonts/', fontFamily: 'Glyphicons Halflings'});
+ };
+ };
+})();
diff --git a/lib/plugin/ui/lib/ui-lib-ff-spa-loader.js b/lib/plugin/ui/lib/ui-lib-ff-spa-loader.js
new file mode 100644
index 0000000..690f0c3
--- /dev/null
+++ b/lib/plugin/ui/lib/ui-lib-ff-spa-loader.js
@@ -0,0 +1,20 @@
+
+module.exports = (function () {
+
+ return function UILibSpaLoaderPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiLibFFSpaLoader';
+ ctx.description='Adds FFSpaLoader lib to resources.';
+ ctx.localDir = __dirname;
+ ctx.dependencies.push('serverConfigResourcesWeb');
+ ctx.dependencies.push('serverConfigResourcesMobile'); // TODO: is this opt ?
+ };
+
+ this.configServer = function(ctx) {
+ //ctx.hostFileJSNodeModule({file: 'es5-ff-spa-loader.js', path: 'es5-ff-spa-loader/dist', registrate: false});
+ ctx.hostFileJSNodeModule({file: 'es5-ff-spa-loader.js', path: '../../es5-ff-spa-loader', registrate: false});
+ ctx.hostFileCSSNodeModule({file: 'es5-ff-spa-loader.css', path: '../../es5-ff-spa-loader', registrate: false});
+ };
+ };
+})();
diff --git a/lib/plugin/ui/lib/ui-lib-flot.js b/lib/plugin/ui/lib/ui-lib-flot.js
new file mode 100644
index 0000000..9c7c662
--- /dev/null
+++ b/lib/plugin/ui/lib/ui-lib-flot.js
@@ -0,0 +1,39 @@
+
+module.exports = (function () {
+
+ var incHostFileJSNodeModule = function(ctx,includeFile,namePart) {
+ if (includeFile) {
+ ctx.hostFileJSNodeModule({file: 'jquery.flot.'+namePart+'.js',path: 'flot'});
+ }
+ };
+
+ return function UILibFlotPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiLibFlot';
+ ctx.description='Adds basic flot js libs to resources.';
+ ctx.localDir = __dirname;
+ ctx.localConfigTemplate = 'ui-lib-flot.json';
+ ctx.dependencies.push('uiLibJQuery');
+ };
+
+ this.configServer = function(ctx) {
+ ctx.hostFileJSNodeModule({file: 'jquery.flot.js', path: 'flot'});
+
+ var inc = ctx.troot.tmeta.tplugin.uiLibFlot.include;
+ incHostFileJSNodeModule(ctx,inc.categories, 'categories');
+ incHostFileJSNodeModule(ctx,inc.crosshair, 'crosshair');
+ incHostFileJSNodeModule(ctx,inc.errorbars, 'errorbars');
+ incHostFileJSNodeModule(ctx,inc.fillbetween, 'fillbetween');
+ incHostFileJSNodeModule(ctx,inc.image, 'image');
+ incHostFileJSNodeModule(ctx,inc.navigate, 'navigate');
+ incHostFileJSNodeModule(ctx,inc.pie, 'pie');
+ incHostFileJSNodeModule(ctx,inc.resize, 'resize');
+ incHostFileJSNodeModule(ctx,inc.selection, 'selection');
+ incHostFileJSNodeModule(ctx,inc.stack, 'stack');
+ incHostFileJSNodeModule(ctx,inc.symbol, 'symbol');
+ incHostFileJSNodeModule(ctx,inc.threshold, 'threshold');
+ incHostFileJSNodeModule(ctx,inc.time, 'time');
+ };
+ };
+})();
diff --git a/lib/plugin/ui/lib/ui-lib-flot.json b/lib/plugin/ui/lib/ui-lib-flot.json
new file mode 100644
index 0000000..112b4dd
--- /dev/null
+++ b/lib/plugin/ui/lib/ui-lib-flot.json
@@ -0,0 +1,27 @@
+{
+ "masterTEntityTemplate": {
+ "tmeta": { "tplugin": { "uiLibFlot": {
+ "tslug": "uiLibFlot",
+ "include": {
+ "categories": false,
+ "crosshair": false,
+ "errorbars": false,
+ "fillbetween": false,
+ "image": false,
+ "navigate": false,
+ "pie": true,
+ "resize": true,
+ "selection": false,
+ "stack": true,
+ "symbol": false,
+ "threshold": false,
+ "time": true
+ }
+ }}}
+ },
+ "masterTEntityTHelp": {
+ "tmeta": { "tplugin": { "uiLibFlot": {
+ "tslug": "The server debug slug."
+ }}}
+ }
+}
\ No newline at end of file
diff --git a/lib/plugin/ui/lib/ui-lib-fontawesome.js b/lib/plugin/ui/lib/ui-lib-fontawesome.js
new file mode 100644
index 0000000..64bc9ec
--- /dev/null
+++ b/lib/plugin/ui/lib/ui-lib-fontawesome.js
@@ -0,0 +1,25 @@
+
+module.exports = (function () {
+
+ return function UILibBootstrapPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiLibFontAwesome';
+ ctx.description='Adds fontawesome client resources.';
+ };
+
+ this.configServer = function(ctx) {
+ var filter = {
+ '(^.*url.*?;)': '', // removed all urls
+ '(@font-face[\\s\\S]*?})': '', // remove font-face
+ '(^\\s+)': '', // clean up white space
+ '(\\{[\\s\\/]+\\/.*)': '{', // rm comment on functions
+ '(^|\\s\\/\\/.*)': '', // rm comment lines
+ '(\\/\\*[\\s\\*\\!][\\s\\S]*?\\*\\/)': '', // rm comment blocks
+ };
+ ctx.hostFileCSSNodeModule({file: 'font-awesome.css', path: 'font-awesome/css', filterRegex: filter});
+
+ ctx.hostFileCSSFontNodeModule({file: 'fontawesome-webfont.ttf', path: 'font-awesome/fonts/', fontFamily: 'FontAwesome'});
+ };
+ };
+})();
diff --git a/lib/plugin/ui/lib/ui-lib-fontfaceonload.js b/lib/plugin/ui/lib/ui-lib-fontfaceonload.js
new file mode 100644
index 0000000..777b690
--- /dev/null
+++ b/lib/plugin/ui/lib/ui-lib-fontfaceonload.js
@@ -0,0 +1,15 @@
+
+module.exports = (function () {
+
+ return function UILibFontFaceOnLoadPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiLibFontFaceOnLoad';
+ ctx.description='Adds fontfaceonload js lib to resources.';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.hostFileJSNodeModule({file: 'fontfaceonload.js', path: 'fontfaceonload/dist'});
+ };
+ };
+})();
diff --git a/lib/plugin/ui/lib/ui-lib-jquery.js b/lib/plugin/ui/lib/ui-lib-jquery.js
new file mode 100644
index 0000000..dfbdac8
--- /dev/null
+++ b/lib/plugin/ui/lib/ui-lib-jquery.js
@@ -0,0 +1,15 @@
+
+module.exports = (function () {
+
+ return function UILibJQueryPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiLibJQuery';
+ ctx.description='Adds jquery js lib to resources.';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.hostFileJSNodeModule({file: 'jquery.min.js', path: 'jquery/dist'});
+ };
+ };
+})();
diff --git a/lib/plugin/ui/spa/ui-spa-style.js b/lib/plugin/ui/spa/ui-spa-style.js
new file mode 100644
index 0000000..b7aa89a
--- /dev/null
+++ b/lib/plugin/ui/spa/ui-spa-style.js
@@ -0,0 +1,18 @@
+
+module.exports = (function () {
+
+ return function UISpaStylePlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiSpaStyle';
+ ctx.description='Adds basic styling resources.';
+ ctx.dependencies.push('uiSpaTopcoatFont');
+ };
+
+ this.configServer = function(ctx) {
+ ctx.hostTemplateCSS('css/flot');
+ ctx.hostTemplateCSS('css/panel');
+ ctx.hostTemplateCSS('css/style');
+ };
+ };
+})();
diff --git a/lib/plugin/ui/spa/ui-spa-topcoat-font.js b/lib/plugin/ui/spa/ui-spa-topcoat-font.js
new file mode 100644
index 0000000..1b3932b
--- /dev/null
+++ b/lib/plugin/ui/spa/ui-spa-topcoat-font.js
@@ -0,0 +1,19 @@
+
+module.exports = (function () {
+
+ var fontPath = 'topcoat-fonts/font/SourceSansPro/';
+
+ return function UISpaTopcoatFontPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiSpaTopcoatFont';
+ ctx.description='Adds Source Sans Pro fonts.';
+ };
+
+ this.configServer = function(ctx) {
+ ctx.hostFileCSSFontNodeModule({file: 'SourceSansPro-Light.otf', path: fontPath, fontFamily: 'Source Sans', fontWeight: 200});
+ ctx.hostFileCSSFontNodeModule({file: 'SourceSansPro-Regular.otf', path: fontPath, fontFamily: 'Source Sans', fontWeight: 400});
+ ctx.hostFileCSSFontNodeModule({file: 'SourceSansPro-Semibold.otf',path: fontPath, fontFamily: 'Source Sans', fontWeight: 600});
+ };
+ };
+})();
diff --git a/lib/plugin/ui/spa/ui-spa-topcoat.js-old b/lib/plugin/ui/spa/ui-spa-topcoat.js-old
new file mode 100644
index 0000000..aea0844
--- /dev/null
+++ b/lib/plugin/ui/spa/ui-spa-topcoat.js-old
@@ -0,0 +1,24 @@
+
+module.exports = (function () {
+
+ return function UISpaTopcoatPlugin() {
+
+ this.configPlugin = function (ctx) {
+ ctx.key='uiSpaTopcoat';
+ ctx.description='Adds and hosts topcoat.css as client resource.';
+ };
+
+ this.configServer = function(ctx) {
+ var filter = {
+ '(^.*url.*?;)': '', // removed all urls
+ '(@font[\\s\\S]*?})': '', // remove font-face
+ '(body[\\s\\S]*?})': '', // remove body
+ '(^\\s+)': '', // clean up white space
+ '(\\{[\\s\\/]+\\/.*)': '{', // rm comment on functions
+ '(^|\\s\\/\\/.*)': '', // rm comment lines
+ '(\\/\\*[\\s\\*\\!][\\s\\S]*?\\*\\/)': '', // rm comment blocks
+ };
+ ctx.hostFileCSSNodeModule({file: 'topcoat-mobile-dark.css',path: 'topcoat/css', filterRegex: filter});
+ };
+ };
+})();
diff --git a/lib/tcrud-config.js b/lib/tcrud-config.js
new file mode 100644
index 0000000..ed3bbac
--- /dev/null
+++ b/lib/tcrud-config.js
@@ -0,0 +1,79 @@
+var debug = require('debug')('ff:tcrud:config');
+var configRegistry = require('./config-registry');
+var configUtil = require('./config-util');
+
+var mod = (function () {
+
+ return function TCrudConfig() {
+
+ this.getClientJSResources = function() {
+ return configRegistry.getMasterConfig().clientJSResources;
+ };
+
+ this.getClientCSSResources = function() {
+ return configRegistry.getMasterConfig().clientCSSResources;
+ };
+
+ this.getBackend = function(backendId) {
+ return configRegistry.getMasterConfig().backends[backendId];
+ };
+
+ this.getRootTEntity = function() {
+ return configRegistry.getRootTEntity();
+ };
+
+ this.createTEntityNode = function(parent, tid, modelKeys, viewKeys) {
+ configRegistry.assertPhaseConfig();
+ var result = configUtil.clone(parent);
+ result.tchilds = [];
+
+ result.tid = tid;
+ parent.tchilds.push(result);
+ result.tparent = parent;
+ if (modelKeys) {
+ if (Array.isArray(modelKeys)) {
+ result.tmeta.tmodel.tkeys = modelKeys;
+ } else {
+ result.tmeta.tmodel.tkeys.push(modelKeys);
+ }
+ }
+ if (viewKeys) {
+ if (Array.isArray(modelKeys)) {
+ result.tkeys = viewKeys;
+ } else {
+ result.tkeys.push(viewKeys);
+ }
+ }
+ return result;
+ };
+
+ this.createTEntity = function(parent, tid, modelKeys, viewKeys) {
+ configRegistry.assertPhaseConfig();
+ debug('createTEntity.tid: %s',tid);
+ var result = module.exports.createTEntityNode(parent,tid,modelKeys,viewKeys);
+ result.tmeta.tmodel.tid = tid;
+ return result;
+ };
+
+ this.createTField = function(parent, tid) {
+ configRegistry.assertPhaseConfig();
+ debug('createTField.tid: %s',tid);
+ var result = configUtil.clone(configRegistry.getMasterTemplates().masterTFieldTemplate);
+ result.tid = tid;
+ parent.tmeta.tfields[tid] = result;
+ return result;
+ };
+
+ this.createTView = function(tentity) {
+ debug('createTView: %s',tentity.tid);
+ configRegistry.assertPhaseServer();
+ var result = configUtil.clone(configRegistry.getMasterTemplates().masterTViewTemplate);
+ result.tid = tentity.tid;
+ configUtil.copyByTemplate('tview',result,tentity,configRegistry.getMasterTemplates().masterTViewTemplate);
+ configRegistry.pluginCall('fillTView',{tview: result,tentity: tentity});
+ return result;
+ }
+ };
+})();
+
+module.exports = new mod;
diff --git a/lib/tcrud-setup.js b/lib/tcrud-setup.js
new file mode 100644
index 0000000..aa2581f
--- /dev/null
+++ b/lib/tcrud-setup.js
@@ -0,0 +1,139 @@
+var express = require('express');
+var bodyParser = require('body-parser');
+var path = require('path');
+var debug = require('debug')('ff:tcrud:setup');
+var configRegistry = require('./config-registry');
+var buildServerApi = require('./build-server-api');
+var buildServerApiTEntity = require('./build-server-api-tentity');
+
+var mod = (function () {
+
+ var server = null;
+
+ var expressCreate = function() {
+ if (server === null) {
+ server = express();
+ } else {
+ throw new Error('Can only create once.');
+ }
+ };
+
+ var expressBuild = function(options) {
+ if (!options) {
+ options = {
+ viewsDir: null
+ };
+ }
+
+ var viewsDirs = [];
+ viewsDirs.push(path.join(__dirname, 'www_views'));
+ if (options.viewsDir) {
+ viewsDirs.push(options.viewsDir);
+ }
+ server.set('view engine', 'ejs');
+ server.set('views', viewsDirs);
+ debug('build.views: '+viewsDirs);
+
+ server.use(bodyParser.json());
+ server.use(bodyParser.urlencoded());
+ debug('build.bodyParser');
+
+ buildServerApi.build(server);
+ buildServerApiTEntity.build(server);
+ debug('build.serverApi');
+ };
+
+ var expressListen = function() {
+ var tcrud = configRegistry.getRootTEntity();
+ var serverPortIndex = tcrud.tmeta.tserver.thost.indexOf(':',6); // TODO: make robust
+ var serverPort = tcrud.tmeta.tserver.thost.substring(serverPortIndex+1);
+ debug('listen port: '+serverPort);
+ server.listen(serverPort);
+ return serverPort;
+ }
+
+ return function TCrudSetup() {
+
+ this.pluginLoad = function(plugin) {
+ configRegistry.assertPhaseInit();
+ configRegistry.pluginLoad(plugin);
+ };
+
+ this.pluginEnable = function(pluginKey) {
+ configRegistry.assertPhaseInit();
+ configRegistry.pluginEnable(pluginKey);
+ };
+
+ this.pluginEnableAll = function() {
+ configRegistry.assertPhaseInit();
+ configRegistry.pluginEnable('.*');
+ };
+
+ this.pluginDisable = function(pluginKey) {
+ configRegistry.assertPhaseInit();
+ configRegistry.pluginEnable(pluginKey,false);
+ };
+
+
+
+ this.phaseConfig = function() {
+ configRegistry.assertPhaseInit();
+ configRegistry.phaseNext();
+ };
+
+ this.phaseServer = function() {
+ configRegistry.assertPhaseConfig();
+ configRegistry.phaseNext();
+ };
+
+ this.phaseServerUp = function() {
+ configRegistry.assertPhaseServer();
+ configRegistry.phaseNext();
+
+ var ctx = {
+ server: server
+ }
+ configRegistry.pluginCall('configPostBoot',ctx);
+ };
+
+
+ this.expressSimple = function(options) {
+ expressCreate();
+ expressBuild(options);
+ return expressListen();
+ }
+
+ this.expressCreate = function() {
+ configRegistry.assertPhaseServer();
+ expressCreate();
+ return server;
+ };
+
+ this.expressBuild = function(options) {
+ configRegistry.assertPhaseServer();
+ expressBuild(options);
+ };
+
+ this.expressListen = function() {
+ configRegistry.assertPhaseServer();
+ return expressListen();
+ };
+ };
+})();
+
+module.exports = new mod;
+
+//
+//ConfigSetupExpress.prototype.renderTemplatePath = function(viewPath) {
+// if (!viewPath) {
+// viewPath = '';
+// }
+// return function (req, res) {
+// res.locals.query = req.query;
+// var qi = req.url.indexOf('?');
+// if (qi === -1) {
+// qi = req.url.length;
+// }
+// res.render(viewPath + req.url.substring(req.route.path.length-1, qi));
+// };
+//}
diff --git a/lib/www_views/node-ff-tcrud/angular/index.ejs b/lib/www_views/node-ff-tcrud/angular/index.ejs
new file mode 100644
index 0000000..c4787f9
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/index.ejs
@@ -0,0 +1,18 @@
+
+
+
+
+ Loading
+
+
+
+
+
+
+
+
diff --git a/lib/www_views/node-ff-tcrud/angular/js/application-controller.ejs b/lib/www_views/node-ff-tcrud/angular/js/application-controller.ejs
new file mode 100644
index 0000000..3c7c0d2
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/js/application-controller.ejs
@@ -0,0 +1,14 @@
+'use strict';
+
+tcrudUI.controller('ApplicationController',function($scope,$http,$location,navigationService) {
+ console.log('start menu controller');
+ $scope.goLink = function ( path ) {
+ $location.path( path );
+ };
+ $scope.navigationService = navigationService;
+ $scope.applicationMenu = {};
+ $http.get('<%= troot.tmeta.tserver.tslugs.tbase %>/<%= troot.tmeta.tserver.tslugs.tserver %>/config/menu').success(function(data, status, headers, config) {
+ $scope.applicationMenu = data.data;
+ $scope.applicationMenuKeys = Object.keys(data.data);
+ });
+});
diff --git a/lib/www_views/node-ff-tcrud/angular/js/application-font.ejs b/lib/www_views/node-ff-tcrud/angular/js/application-font.ejs
new file mode 100644
index 0000000..f1ecf50
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/js/application-font.ejs
@@ -0,0 +1,15 @@
+'use strict';
+
+console.log('FFTCrudExample.fontFaceOnload check');
+var ffolStartTime = new Date().getTime();
+FontFaceOnload('Source Sans', {
+ timeout: 1234,
+ success: function() {
+ console.log('FFTCrudExample.FontFaceOnload success in '+(new Date().getTime()-ffolStartTime)+' ms.');
+ document.documentElement.className += ' fontLoaded';
+ },
+ error: function() {
+ console.log('FFTCrudExample.fontFaceOnload timeout.');
+ document.documentElement.className += ' fontLoaded';
+ }
+});
diff --git a/lib/www_views/node-ff-tcrud/angular/js/application.ejs b/lib/www_views/node-ff-tcrud/angular/js/application.ejs
new file mode 100644
index 0000000..5f6a5d0
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/js/application.ejs
@@ -0,0 +1,26 @@
+'use strict';
+
+var serverUrl = window.FFServerUrl;
+var crudRouteInit = [];
+var pageRouteInit = [];
+
+document.title = 'TCrud Example';
+
+$(document.createElement('div')).attr('ng-controller', 'ApplicationController').attr('ng-include','\''+serverUrl+'/api/plugin/angular/thtml/application-top\'').appendTo($('body'));
+$(document.createElement('div')).attr('ng-include','\''+serverUrl+'/api/plugin/angular/thtml/application-view\'').appendTo($('body'));
+
+var tcrudUI = angular.module('tcrudUI', ['ngRoute','ngTouch','ngAnimate', 'ui.grid', 'ui.grid.pagination','ui.grid.cellNav', 'ui.grid.edit', 'ui.grid.resizeColumns', 'ui.grid.pinning', 'ui.grid.selection', 'ui.grid.moveColumns', 'ui.grid.exporter', 'ui.grid.importer', 'ui.grid.grouping', 'ui.grid.autoResize']).
+ config(['$routeProvider', '$locationProvider', '$sceDelegateProvider', function($routeProvider, $locationProvider, $sceDelegateProvider) {
+
+ // init routes
+ pageRouteInit.forEach(function(init) { init($routeProvider, $locationProvider); });
+ crudRouteInit.forEach(function(init) { init($routeProvider, $locationProvider); });
+
+ $sceDelegateProvider.resourceUrlWhitelist(['self',serverUrl+'/**']);
+ $routeProvider.otherwise({ redirectTo: '/ui' });
+ $locationProvider.html5Mode({requireBase: false});
+ }]);
+
+tcrudUI.run(['$route', function($route) {
+ $route.reload(); // ng-view works inside the ng-include
+}]);
diff --git a/lib/www_views/node-ff-tcrud/angular/js/crud/controller-action-none.ejs b/lib/www_views/node-ff-tcrud/angular/js/crud/controller-action-none.ejs
new file mode 100644
index 0000000..3927783
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/js/crud/controller-action-none.ejs
@@ -0,0 +1,8 @@
+
+ $scope.<%= taction %>None = function () {
+ <% if (tview.tlist) { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/<%= tview.tlist.tplugin.angular.tslug %>');
+ <% } else { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/');
+ <% } %>
+ }
diff --git a/lib/www_views/node-ff-tcrud/angular/js/crud/controller-route.ejs b/lib/www_views/node-ff-tcrud/angular/js/crud/controller-route.ejs
new file mode 100644
index 0000000..ead5e69
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/js/crud/controller-route.ejs
@@ -0,0 +1,5 @@
+
+ $routeProvider.when('<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/<%= tview[taction].tplugin.angular.tslug %><%= routeEnd %>', {
+ templateUrl: '<%= thtmlPrefix %>/<%= tview[taction].tplugin.angular.thtml %>',
+ controller: <%= tview[taction].tplugin.angular.tcontroller.prefix %><%= tviewCode %><%= tview[taction].tplugin.angular.tcontroller.postfix %>
+ });
\ No newline at end of file
diff --git a/lib/www_views/node-ff-tcrud/angular/js/crud/controller.ejs b/lib/www_views/node-ff-tcrud/angular/js/crud/controller.ejs
new file mode 100644
index 0000000..ef4ff0d
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/js/crud/controller.ejs
@@ -0,0 +1,173 @@
+'use strict';
+
+//
+// Auto generated controller mapping for: <%= tview.tid %>
+//
+
+crudRouteInit.push(auto<%= tviewCode %>Init);
+
+function auto<%= tviewCode %>Init($routeProvider, $locationProvider) {
+<% if (tview.tlist) { %>
+ $routeProvider.when('<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/', {
+ redirectTo: '<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/<%= tview.tlist.tplugin.angular.tslug %>'
+ });
+ <%- include('controller-route', {tview: tview,tviewCode: tviewCode,thtmlPrefix:thtmlPrefix,taction: 'tlist', routeEnd: ''}); %>
+<% } %>
+<% if (tview.tcreate) { %>
+ <%- include('controller-route', {tview: tview,tviewCode: tviewCode,thtmlPrefix:thtmlPrefix,taction: 'tcreate', routeEnd: ''}); %>
+<% } %>
+<% if (tview.tedit) { %>
+ <%- include('controller-route', {tview: tview,tviewCode: tviewCode,thtmlPrefix:thtmlPrefix,taction: 'tedit', routeEnd: '/'+tview.tmeta.tmodel.tkey}); %>
+<% } %>
+<% if (tview.tread) { %>
+ <%- include('controller-route', {tview: tview,tviewCode: tviewCode,thtmlPrefix:thtmlPrefix,taction: 'tread', routeEnd: '/'+tview.tmeta.tmodel.tkey}); %>
+<% } %>
+<% if (tview.tdelete) { %>
+ <%- include('controller-route', {tview: tview,tviewCode: tviewCode,thtmlPrefix:thtmlPrefix,taction: 'tdelete', routeEnd: '/'+tview.tmeta.tmodel.tkey}); %>
+<% } %>
+}
+
+<% if (tview.tlist) { %>
+function <%= tview.tlist.tplugin.angular.tcontroller.prefix %><%= tviewCode %><%= tview.tlist.tplugin.angular.tcontroller.postfix %>(<%= tview.tlist.tplugin.angular.tcontroller.argu %>) {
+
+ $scope.uiTableMain = {};
+ $scope.uiTableMain.enableColumnResizing = true;
+ $scope.uiTableMain.enableFiltering = false;
+ $scope.uiTableMain.enableGridMenu = true;
+ $scope.uiTableMain.showGridFooter = false;
+ $scope.uiTableMain.showColumnFooter = false;
+ $scope.uiTableMain.paginationPageSizes = [25, 50, 75];
+ $scope.uiTableMain.paginationPageSize = 25;
+ $scope.uiTableMain.enableFullRowSelection = true;
+ $scope.uiTableMain.multiSelect = false;
+ $scope.uiTableMain.columnDefs = [];
+
+ $scope.uiTableMain.onRegisterApi = function(gridApi){
+ $scope.gridApi = gridApi;
+
+ gridApi.selection.on.rowSelectionChanged($scope,function(row) {
+ if ($scope.gridApi.selection.getSelectedRows().length > 0) {
+ navigationService.actions.open = $scope.doEdit;
+ } else {
+ navigationService.actions.open = null;
+ }
+
+ });
+ };
+
+ var fetchData = function() {
+ $http.get('<%= tapiPrefix %>/<%= tview.tlist.tplugin.formatJSON.tslug %>').success(function(data, status, headers, config) {
+ $scope.uiTableMain.data = data.data;
+ });
+ };
+
+ navigationService.actions.refresh = function() {
+ fetchData();
+ };
+ navigationService.actions.create = function() {
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/<%= tview.tcreate.tplugin.angular.tslug %>');
+ };
+ $scope.doEdit = function() {
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/<%= tview.tedit.tplugin.angular.tslug %>/'+$scope.gridApi.selection.getSelectedRows()[0].country_id);
+ };
+
+ $http.get('<%= tapiPrefix2 %>').success(function(data, status, headers, config) {
+ var tview = data.data.tview;
+ tview.tlist.tfields.forEach(function (fieldKey) {
+ var field = tview.tmeta.tfields[fieldKey];
+ $scope.uiTableMain.columnDefs.push({
+ name: field.tname,
+ field: field.tid,
+ type2: field.ttype,
+ });
+ });
+ navigationService.pageTitle = tview.tname;
+ fetchData();
+ });
+}
+<% } %>
+
+<% if (tview.tcreate) { %>
+function <%= tview.tcreate.tplugin.angular.tcontroller.prefix %><%= tviewCode %><%= tview.tcreate.tplugin.angular.tcontroller.postfix %>(<%= tview.tcreate.tplugin.angular.tcontroller.argu %>) {
+ $scope.data = {};
+ $scope.tcreateData = function () {
+ $http.post('<%= tapiPrefix %>/<%= tview.tcreate.tplugin.formatJSON.tslug %>', $scope.data).success(function(data) {
+ <% if (tview.tlist) { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/<%= tview.tlist.tplugin.angular.tslug %>');
+ <% } else { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/');
+ <% } %>
+ });
+ }
+ <%- include('controller-action-none', {tview: tview,taction: 'tcreate'}); %>
+}
+<% } %>
+
+<% if (tview.tread) { %>
+function <%= tview.tread.tplugin.angular.tcontroller.prefix %><%= tviewCode %><%= tview.tread.tplugin.angular.tcontroller.postfix %>(<%= tview.tread.tplugin.angular.tcontroller.argu %>) {
+ $scope.data = {};
+ $http.get('<%= tapiPrefix %>/<%= tview.tread.tplugin.formatJSON.tslug %>/' + <%= ejsRouteParams %>).success(function(data) {
+ $scope.data = data.data;
+ });
+ <%- include('controller-action-none', {tview: tview,taction: 'tread'}); %>
+}
+<% } %>
+
+<% if (tview.tedit) { %>
+function <%= tview.tedit.tplugin.angular.tcontroller.prefix %><%= tviewCode %><%= tview.tedit.tplugin.angular.tcontroller.postfix %>(<%= tview.tedit.tplugin.angular.tcontroller.argu %>) {
+ $scope.data = {};
+ var fetchData = function() {
+ $http.get('<%= tapiPrefix %>/<%= tview.tread.tplugin.formatJSON.tslug %>/' + <%= ejsRouteParams %>).success(function(data) {
+ $scope.data = data.data;
+ });
+ };
+ fetchData();
+
+ $scope.teditData = function () {
+ $http.put('<%= tapiPrefix %>/<%= tview.tedit.tplugin.formatJSON.tslug %>/' + <%= ejsRouteParams %>, $scope.data ).success(function(data) {
+ <% if (tview.tread) { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/<%= tview.tread.tplugin.angular.tslug %>/' + <%= ejsRouteParams %>);
+ <% } else { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/');
+ <% } %>
+ });
+ }
+ $scope.tdeleteData = function () {
+ $http.delete('<%= tapiPrefix %>/<%= tview.tdelete.tplugin.formatJSON.tslug %>/'+ <%= ejsRouteParams %>, $scope.data).success(function(data) {
+ <% if (tview.tlist) { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/<%= tview.tlist.tplugin.angular.tslug %>');
+ <% } else { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/');
+ <% } %>
+ });
+ }
+ <%- include('controller-action-none', {tview: tview,taction: 'tedit'}); %>
+
+ navigationService.actions.save = $scope.teditData;
+ navigationService.actions.cancel = $scope.teditNone;
+ navigationService.actions.delete = $scope.tdeleteData;
+ navigationService.actions.refresh = function() {
+ fetchData();
+ };
+}
+<% } %>
+
+<% if (tview.tdelete) { %>
+function <%= tview.tdelete.tplugin.angular.tcontroller.prefix %><%= tviewCode %><%= tview.tdelete.tplugin.angular.tcontroller.postfix %>(<%= tview.tdelete.tplugin.angular.tcontroller.argu %>) {
+ $scope.data = {};
+ $http.get('<%= tapiPrefix %>/<%= tview.tread.tplugin.formatJSON.tslug %>/' + <%= ejsRouteParams %>).success(function(data) {
+ $scope.data = data.data;
+ console.log('delete obj: '+JSON.stringify(data.data));
+ });
+ $scope.tdeleteData = function () {
+ $http.delete('<%= tapiPrefix %>/<%= tview.tdelete.tplugin.formatJSON.tslug %>/'+ <%= ejsRouteParams %>, $scope.data).success(function(data) {
+ <% if (tview.tlist) { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/<%= tview.tslug %>/<%= tview.tlist.tplugin.angular.tslug %>');
+ <% } else { %>
+ $location.url('<%= tview.tmeta.tplugin.angular.tbase %>/');
+ <% } %>
+ });
+ }
+ <%- include('controller-action-none', {tview: tview,taction: 'tdelete'}); %>
+}
+<% } %>
diff --git a/lib/www_views/node-ff-tcrud/angular/js/navigation-service.ejs b/lib/www_views/node-ff-tcrud/angular/js/navigation-service.ejs
new file mode 100644
index 0000000..bfbec88
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/js/navigation-service.ejs
@@ -0,0 +1,57 @@
+'use strict';
+
+tcrudUI.factory('navigationService', ['$route', '$rootScope',
+ function($route, $rootScope) {
+
+ var actions = {
+ save: null,
+ cancel: null,
+ open: null,
+ delete: null,
+ create: null,
+ refresh: null
+ };
+ var resetActions = function() {
+ for (var key in actions) {
+ if (key.indexOf('Impl') > 0) {
+ continue;
+ }
+ actions[key] = null;
+ }
+ };
+ actions.saveImpl = function() {
+ actions.save();
+ resetActions();
+ };
+ actions.cancelImpl = function() {
+ actions.cancel();
+ resetActions();
+ };
+ actions.openImpl = function() {
+ actions.open();
+ resetActions();
+ };
+ actions.deleteImpl = function() {
+ actions.delete();
+ resetActions();
+ };
+
+ var pageTabs = [];
+ var pageTitle = '';
+ var pageLocations = [];
+
+ $rootScope.$on('$routeChangeSuccess', function() {
+ pageTabs = [];
+ pageTitle = '';
+ pageLocations = [];
+ resetActions(); // to late gives flikering..
+ //$rootScope.$apply();
+ });
+ return {
+ actions: actions,
+ pageTabs: pageTabs,
+ pageTitle: pageTitle,
+ pageLocations: pageLocations,
+ };
+ }
+]);
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/application-top-action.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/application-top-action.ejs
new file mode 100644
index 0000000..cc88972
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/application-top-action.ejs
@@ -0,0 +1,20 @@
+
+
+
+
+ New
+
+
+
+
+
+
+
+
+ Open
+
+
+
+
+
+
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/application-top-tabs.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/application-top-tabs.ejs
new file mode 100644
index 0000000..af2f065
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/application-top-tabs.ejs
@@ -0,0 +1,9 @@
+
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/application-top.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/application-top.ejs
new file mode 100644
index 0000000..74172b8
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/application-top.ejs
@@ -0,0 +1,69 @@
+
+
+
+
{{navigationService.pageTitle}}
+
+
+
+
+
+
+
Are you sure to delete this record ?
+
+
+
+
+
+
+
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/application-view.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/application-view.ejs
new file mode 100644
index 0000000..68649ed
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/application-view.ejs
@@ -0,0 +1,20 @@
+
\ No newline at end of file
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/crud/create.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/crud/create.ejs
new file mode 100644
index 0000000..39dccba
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/crud/create.ejs
@@ -0,0 +1,16 @@
+Create <%= tview.tname %>
+
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/crud/delete.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/crud/delete.ejs
new file mode 100644
index 0000000..c1b3152
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/crud/delete.ejs
@@ -0,0 +1,6 @@
+Delete <%= tview.tname %>
+
+
Are you sure you want to delete this <%= tview.tname %> <%= ejsKeyData %> ?
+
Yes | -
+
No thanks
+
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/crud/edit.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/crud/edit.ejs
new file mode 100644
index 0000000..76f5e54
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/crud/edit.ejs
@@ -0,0 +1,12 @@
+
+
Edit <%= tview.tname %>
+
+
+
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/crud/list.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/crud/list.ejs
new file mode 100644
index 0000000..ebb4507
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/crud/list.ejs
@@ -0,0 +1 @@
+
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/crud/list2.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/crud/list2.ejs
new file mode 100644
index 0000000..fc36b05
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/crud/list2.ejs
@@ -0,0 +1,50 @@
+
+
There are {{data.length}} <%= tview.tplural %>
+
+<% if (tview.tcreate) { %>
+
+ Create New
+
+<% } %>
+
+
+
+
+ <% if (tview.tread) { %>Open <% } %>
+ <% if (tview.tedit) { %>Edit <% } %>
+ <% if (tview.tdelete) { %>Delete <% } %>
+ <% tview.tlist.tfields.forEach(function (fieldKey) { %>
+ <%= tview.tmeta.tfields[fieldKey].tname %>
+ <% }) %>
+
+
+
+
+ <% if (tview.tread) { %>
+
+ Read
+
+ <% } %>
+ <% if (tview.tedit) { %>
+
+ Edit
+
+ <% } %>
+ <% if (tview.tdelete) { %>
+
+ Delete
+
+ <% } %>
+ <% tview.tlist.tfields.forEach(function (fieldKey) { %>
+ {{row.<%= tview.tmeta.tfields[fieldKey].tid %>}}
+ <% }) %>
+
+
+
+
+<% if (tview.tcreate) { %>
+
+ Create New
+
+<% } %>
+
diff --git a/lib/www_views/node-ff-tcrud/angular/thtml/crud/read.ejs b/lib/www_views/node-ff-tcrud/angular/thtml/crud/read.ejs
new file mode 100644
index 0000000..c577ddd
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/angular/thtml/crud/read.ejs
@@ -0,0 +1,19 @@
+
+ <%= ejsKeyData %>
+ <% if (tview.tedit) { %>
+
+ Edit
+
+ <% } %>
+ <% if (tview.tdelete) { %>
+
+ Delete
+
+ <% } %>
+ <% tview.tread.tfields.forEach(function (fieldKey) { %>
+ <%= tview.tmeta.tfields[fieldKey].tname %>: {{data.<%= tview.tmeta.tfields[fieldKey].tid %>}}
+ <% }) %>
+
+ Back
+
+
\ No newline at end of file
diff --git a/lib/www_views/node-ff-tcrud/formatCSV/list-footer.ejs b/lib/www_views/node-ff-tcrud/formatCSV/list-footer.ejs
new file mode 100644
index 0000000..ccfc2e0
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatCSV/list-footer.ejs
@@ -0,0 +1 @@
+# CSV END
diff --git a/lib/www_views/node-ff-tcrud/formatCSV/list-header.ejs b/lib/www_views/node-ff-tcrud/formatCSV/list-header.ejs
new file mode 100644
index 0000000..dee6269
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatCSV/list-header.ejs
@@ -0,0 +1,2 @@
+# CSV <%= tview.tid %>
+# <% tview.tlist.tfields.forEach(function (tfieldKey) {var tfield = tview.tmeta.tfields[tfieldKey]; %><%= tfield.tid %>,<% }) %>
diff --git a/lib/www_views/node-ff-tcrud/formatCSV/list-record.ejs b/lib/www_views/node-ff-tcrud/formatCSV/list-record.ejs
new file mode 100644
index 0000000..29f92bf
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatCSV/list-record.ejs
@@ -0,0 +1 @@
+<% tview.tlist.tfields.forEach(function (tfieldKey) {var tfield = tview.tmeta.tfields[tfieldKey]; %><%= record[tfield.tid] %>,<% }) %>
diff --git a/lib/www_views/node-ff-tcrud/formatCSV/read-record.ejs b/lib/www_views/node-ff-tcrud/formatCSV/read-record.ejs
new file mode 100644
index 0000000..29f92bf
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatCSV/read-record.ejs
@@ -0,0 +1 @@
+<% tview.tlist.tfields.forEach(function (tfieldKey) {var tfield = tview.tmeta.tfields[tfieldKey]; %><%= record[tfield.tid] %>,<% }) %>
diff --git a/lib/www_views/node-ff-tcrud/formatRSS/list-footer.ejs b/lib/www_views/node-ff-tcrud/formatRSS/list-footer.ejs
new file mode 100644
index 0000000..29f01a4
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatRSS/list-footer.ejs
@@ -0,0 +1 @@
+<%= tview.tid %>List>
diff --git a/lib/www_views/node-ff-tcrud/formatRSS/list-header.ejs b/lib/www_views/node-ff-tcrud/formatRSS/list-header.ejs
new file mode 100644
index 0000000..21e1b5d
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatRSS/list-header.ejs
@@ -0,0 +1,2 @@
+
+<<%= tview.tid %>List>
diff --git a/lib/www_views/node-ff-tcrud/formatRSS/list-record.ejs b/lib/www_views/node-ff-tcrud/formatRSS/list-record.ejs
new file mode 100644
index 0000000..5f59b36
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatRSS/list-record.ejs
@@ -0,0 +1,5 @@
+ <<%= tview.tid %>>
+ <% Object.keys(tview.tlist.tfields).forEach(function (tfieldKey) {var tfield = tview.tlist.tfields[tfieldKey]; %>
+ <<%= tfieldKey %> type="<%= tfield.type %>"><%= record[tfield.tid] %><%= tfieldKey %>>
+ <% }) %>
+ <%= tview.tid %>>
\ No newline at end of file
diff --git a/lib/www_views/node-ff-tcrud/formatXML/list-footer.ejs b/lib/www_views/node-ff-tcrud/formatXML/list-footer.ejs
new file mode 100644
index 0000000..1a598fc
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatXML/list-footer.ejs
@@ -0,0 +1,2 @@
+ <%= tview.tid %>List>
+
diff --git a/lib/www_views/node-ff-tcrud/formatXML/list-header.ejs b/lib/www_views/node-ff-tcrud/formatXML/list-header.ejs
new file mode 100644
index 0000000..e6b4381
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatXML/list-header.ejs
@@ -0,0 +1,3 @@
+
+
+ <<%= tview.tid %>List>
diff --git a/lib/www_views/node-ff-tcrud/formatXML/list-record.ejs b/lib/www_views/node-ff-tcrud/formatXML/list-record.ejs
new file mode 100644
index 0000000..4aead38
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatXML/list-record.ejs
@@ -0,0 +1,5 @@
+ <<%= tview.tid %>>
+ <% tview.tlist.tfields.forEach(function (tfieldKey) {var tfield = tview.tmeta.tfields[tfieldKey]; %>
+ <<%= tfield.tid %> type="<%= tfield.type %>"><%= record[tfield.tid] %><%= tfield.tid %>>
+ <% }) %>
+ <%= tview.tid %>>
\ No newline at end of file
diff --git a/lib/www_views/node-ff-tcrud/formatXML/read-record.ejs b/lib/www_views/node-ff-tcrud/formatXML/read-record.ejs
new file mode 100644
index 0000000..61496a1
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/formatXML/read-record.ejs
@@ -0,0 +1,8 @@
+
+
+ <<%= tview.tid %>>
+ <% tview.tlist.tfields.forEach(function (tfieldKey) {var tfield = tview.tmeta.tfields[tfieldKey]; %>
+ <<%= tfield.tid %> type="<%= tfield.type %>"><%= record[tfield.tid] %><%= tfield.tid %>>
+ <% }) %>
+ <%= tview.tid %>>
+
diff --git a/lib/www_views/node-ff-tcrud/uiAngularServerPlugins/js/server-plugins.ejs b/lib/www_views/node-ff-tcrud/uiAngularServerPlugins/js/server-plugins.ejs
new file mode 100644
index 0000000..737df61
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/uiAngularServerPlugins/js/server-plugins.ejs
@@ -0,0 +1,45 @@
+'use strict';
+
+//
+// Auto generated controller server plugin controller
+//
+
+crudRouteInit.push(function ($routeProvider, $locationProvider) {
+ $routeProvider.when('/ui/server/plugins', {
+ templateUrl: '<%= troot.tmeta.tserver.tslugs.tbase %>/<%= troot.tmeta.tserver.tslugs.tplugin %>/uiAngularServerPlugins/thtml/plugins',
+ controller: XPageServerPlugins,
+ });
+});
+
+function XPageServerPlugins($scope, $http) {
+ $scope.message = '';
+ $scope.doReload = function () {
+ window.location.reload(true);
+ };
+
+ $scope.doClearServerUrl = function () {
+ FFSpaLoader.clearServerUrl(function(err) {
+ if (err) {
+ $scope.message = 'Error: '+err;
+ } else {
+ $scope.message = 'Cleared server url';
+ }
+ $scope.$apply();
+ });
+ };
+
+ $scope.doClearCache = function () {
+ FFSpaLoader.clearCache(function(err) {
+ if (err) {
+ $scope.message = 'Error: '+err;
+ } else {
+ $scope.message = 'Cleared cache';
+ }
+ $scope.$apply();
+ });
+ };
+
+ $http.get('<%= troot.tmeta.tserver.tslugs.tbase %>/<%= troot.tmeta.tserver.tslugs.tserver %>/<%= troot.tmeta.tplugin.serverInfoPlugins.tslug %>').success(function(data, status, headers, config) {
+ $scope.serverPlugins = data.data;
+ });
+}
diff --git a/lib/www_views/node-ff-tcrud/uiAngularServerPlugins/thtml/plugins.ejs b/lib/www_views/node-ff-tcrud/uiAngularServerPlugins/thtml/plugins.ejs
new file mode 100644
index 0000000..bc0f0a0
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/uiAngularServerPlugins/thtml/plugins.ejs
@@ -0,0 +1,26 @@
+Server Plugins
+This page show all the tcrud plugins.
+
+
+
+
+
+ Plugin
+ Description
+ Dependencies
+
+
+
+
+ {{plugin.tmeta.key}}
+ {{plugin.tmeta.description}}
+ {{plugin.tmeta.dependencies}}
+
+
+
+
\ No newline at end of file
diff --git a/lib/www_views/node-ff-tcrud/uiAngularServerRoutes/js/server-routes.ejs b/lib/www_views/node-ff-tcrud/uiAngularServerRoutes/js/server-routes.ejs
new file mode 100644
index 0000000..bde6f4a
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/uiAngularServerRoutes/js/server-routes.ejs
@@ -0,0 +1,31 @@
+'use strict';
+
+//
+// Auto generated controller server router controller
+//
+
+crudRouteInit.push(function ($routeProvider, $locationProvider) {
+ $routeProvider.when('/ui/server/routes', {
+ redirectTo: '/ui/server/routes/tech',
+ });
+ $routeProvider.when('/ui/server/routes/tech', {
+ templateUrl: '<%= troot.tmeta.tserver.tslugs.tbase %>/<%= troot.tmeta.tserver.tslugs.tplugin %>/uiAngularServerRoutes/thtml/routes?group1=all,api_server,api_plugin,tentity_rss,tentity_json&group2=tentity_csv,tentity_xml,tentity_angular,tentity_config',
+ controller: XPageServerTechRoutes,
+ });
+ $routeProvider.when('/ui/server/routes/model', {
+ templateUrl: '/api/angular/thtml/routes?group1=moviedb,pagila&group2=all',
+ controller: XPageServerModelRoutes,
+ });
+});
+
+function XPageServerTechRoutes($scope, $http) {
+ $http.get('<%= troot.tmeta.tserver.tslugs.tbase %>/<%= troot.tmeta.tserver.tslugs.tserver %>/<%= troot.tmeta.tplugin.serverConfigRoutes.tslug %>?groups=api/server,api/plugin,tentity/json,tentity/xml,tentity/rss,tentity/csv,tentity/angular,tentity/config').success(function(data, status, headers, config) {
+ $scope.serverRoutes = data.data;
+ });
+}
+
+function XPageServerModelRoutes($scope, $http) {
+ $http.get('<%= troot.tmeta.tserver.tslug %>/json/server/routes?groups=moviedb,pagila').success(function(data, status, headers, config) {
+ $scope.serverRoutes = data.data;
+ });
+}
diff --git a/lib/www_views/node-ff-tcrud/uiAngularServerRoutes/thtml/routes-group.ejs b/lib/www_views/node-ff-tcrud/uiAngularServerRoutes/thtml/routes-group.ejs
new file mode 100644
index 0000000..24c5939
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/uiAngularServerRoutes/thtml/routes-group.ejs
@@ -0,0 +1,20 @@
+
+
<%= routeGroup.toUpperCase() %>
+
+
+
+ Path
+ Method
+
+
+
+
+
+ {{route.uriPath}}
+
+ {{route.uriPath}}
+ {{route.httpMethod}}
+
+
+
+
\ No newline at end of file
diff --git a/lib/www_views/node-ff-tcrud/uiAngularServerRoutes/thtml/routes.ejs b/lib/www_views/node-ff-tcrud/uiAngularServerRoutes/thtml/routes.ejs
new file mode 100644
index 0000000..4456962
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/uiAngularServerRoutes/thtml/routes.ejs
@@ -0,0 +1,26 @@
+Server Routes
+This page show all the nodejs express routes.
+
+ <% if (query.group1 && query.group2) { %>
+
+ <% query.group1.split(',').forEach(function (routeGroup) { %>
+ <%- include('routes-group', {routeGroup: routeGroup}); %>
+ <% }) %>
+
+
+ <% query.group2.split(',').forEach(function (routeGroup) { %>
+ <%- include('routes-group', {routeGroup: routeGroup}); %>
+ <% }) %>
+
+ <% } else if (query.group1) { %>
+
+ <% query.group1.split(',').forEach(function (routeGroup) { %>
+ <%- include('routes-group', {routeGroup: routeGroup}); %>
+ <% }) %>
+
+ <% } else { %>
+
+ <%- include('routes-group', {routeGroup: 'all'}); %>
+
+ <% } %>
+
\ No newline at end of file
diff --git a/lib/www_views/node-ff-tcrud/uiSpaStyle/css/flot.ejs b/lib/www_views/node-ff-tcrud/uiSpaStyle/css/flot.ejs
new file mode 100644
index 0000000..76b6b11
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/uiSpaStyle/css/flot.ejs
@@ -0,0 +1,10 @@
+
+.flot-chart {
+ display: block;
+ height: 400px;
+}
+
+.flot-chart-content {
+ width: 100%;
+ height: 100%;
+}
diff --git a/lib/www_views/node-ff-tcrud/uiSpaStyle/css/panel.ejs b/lib/www_views/node-ff-tcrud/uiSpaStyle/css/panel.ejs
new file mode 100644
index 0000000..7e97652
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/uiSpaStyle/css/panel.ejs
@@ -0,0 +1,56 @@
+
+.panel-green {
+ border-color: #5cb85c;
+}
+
+.panel-green .panel-heading {
+ border-color: #5cb85c;
+ color: #fff;
+ background-color: #5cb85c;
+}
+
+.panel-green a {
+ color: #5cb85c;
+}
+
+.panel-green a:hover {
+ color: #3d8b3d;
+}
+
+.panel-red {
+ border-color: #d9534f;
+}
+
+.panel-red .panel-heading {
+ border-color: #d9534f;
+ color: #fff;
+ background-color: #d9534f;
+}
+
+.panel-red a {
+ color: #d9534f;
+}
+
+.panel-red a:hover {
+ color: #b52b27;
+}
+
+.panel-yellow {
+ border-color: #f0ad4e;
+}
+
+.panel-yellow .panel-heading {
+ border-color: #f0ad4e;
+ color: #fff;
+ background-color: #f0ad4e;
+}
+
+.panel-yellow a {
+ color: #f0ad4e;
+}
+
+.panel-yellow a:hover {
+ color: #df8a13;
+}
+
+
diff --git a/lib/www_views/node-ff-tcrud/uiSpaStyle/css/style.ejs b/lib/www_views/node-ff-tcrud/uiSpaStyle/css/style.ejs
new file mode 100644
index 0000000..76f01d8
--- /dev/null
+++ b/lib/www_views/node-ff-tcrud/uiSpaStyle/css/style.ejs
@@ -0,0 +1,133 @@
+
+.ui-grid {
+ border: none;
+}
+
+.ui-grid-menu-button {
+ border: none;
+ background: none;
+}
+
+.ui-grid-header-cell:last-child .ui-grid-column-resizer.right {
+ border-right: none;
+}
+
+/*
+.fade.ng-hide {
+ transition: all 0s linear 1s;
+}
+.fade.ng-show {
+ transition: all 0s linear 1s;
+}
+*/
+
+body {
+ margin: 0;
+ padding: 0;
+ //background: #4b4d4e;
+ color: #000;
+ font: 16px sans-serif, helvetica, arial;
+ font-weight: 200;
+}
+
+.fontLoaded body {
+ font-family: "Source Sans", sans-serif;
+}
+
+.fillHeightView {
+ display: block;
+ position:absolute;
+ height:auto;
+ bottom:0;
+ top:0;
+ left:0;
+ right:0;
+ padding-top: 120px;
+ min-height: 220px;
+}
+
+.fillHeightCalc {
+ height:100%;
+}
+
+.navbar-menu {
+ position: relative;
+ float: left;
+ margin-right: 15px;
+ padding: 9px 10px;
+ margin-top: 15px;
+ margin-bottom: 15px;
+ background-color: transparent;
+ background-image: none;
+ border: 1px solid transparent;
+ border-radius: 3px;
+}
+
+.navbar-menu .icon-bar {
+ background-color: rgba(0, 0, 0, 0.5);
+}
+.navbar-menu .icon-bar + .icon-bar {
+ margin-top: 4px;
+}
+.navbar-menu .icon-bar {
+ display: block;
+ width: 22px;
+ height: 2px;
+ border-radius: 1px;
+}
+
+.navbar-nav-menu {
+ margin: 0;
+ padding-left: 1em;
+}
+
+.navbar-nav-menu-group {
+ width: 8em;
+ font-weight: bold;
+}
+
+.navbar-collapse {
+ border: none;
+}
+
+.navbar-nav {
+ margin: 0px 0px 0px 0px;
+}
+
+.navbar-nav > li > button {
+ margin-top: 20px;
+ margin-bottom: 20px;
+}
+
+.for-sm-view-hide, navbar-header-right {
+ float: right;
+}
+.for-lg-view-hide, .for-menu-view-hide, .navbar-header-left {
+ float: left;
+}
+
+.for-lg-view-hide > div > ul > li,.for-lg-view-hide > div > div > ul > li {
+ // text-align: right;
+}
+
+@media (max-width: 767px) {
+ .for-sm-view-hide {
+ display: none;
+ }
+ .navbar-nav-menu {
+ float: left;
+ }
+}
+
+@media only screen and (-webkit-min-device-pixel-ratio: 1.3),
+ only screen and (-o-min-device-pixel-ratio: 13/10),
+ only screen and (min-resolution: 120dpi)
+ {
+ font-size:1.2em !important;
+
+ @media screen and (min-width: 1440px) {
+ #hockey2{
+ font-size:1.3em !important;
+ }
+ }
+}
\ No newline at end of file
diff --git a/package.json b/package.json
index 4899930..89a88db 100644
--- a/package.json
+++ b/package.json
@@ -7,7 +7,6 @@
"test": "npm run-script test-clean;npm run-script test-mocha",
"test-clean": "rm test/data/* -rf",
"test-mocha": "export JUNIT_REPORT_PATH=test/data/report.xml;export JUNIT_REPORT_STACK=1;node_modules/mocha/bin/mocha --reporter mocha-jenkins-reporter"
-
},
"author": "Willem (http://forwardfire.net/)",
"license": "BSD-2-Clause",
@@ -16,10 +15,36 @@
"url": "https://bitbucket.org/im_ik/node-ff-tcrud.git"
},
"dependencies": {
- "fetch": "^0.3.6",
+ "angular": "^1.4.8",
+ "angular-animate": "^1.4.8",
+ "angular-resource": "^1.4.8",
+ "angular-route": "^1.4.8",
+ "angular-touch": "^1.4.8",
+ "angular-ui-grid": "^3.1.1",
+ "body-parser": "~1.0.1",
+ "bootstrap": "^3.3.6",
+ "bootswatch": "^3.3.6",
+ "clone": "^1.0.0",
+ "cors": "^2.7.1",
+ "debug": "^2.2.0",
+ "ejs": "^2.3.1",
+ "es5-ff-spa-loader": "0.3.0",
+ "express": "~4.11.0",
+ "fetch": "0.3.x",
+ "flot": "^0.8.0-alpha",
+ "font-awesome": "^4.5.0",
+ "fontfaceonload": "^0.1.7",
+ "fontmin": "^0.9.1",
"fs-extra": "^0.16.3",
- "minify": "^1.4.8",
- "underscore": "^1.8.2"
+ "jquery": "^2.1.4",
+ "node-crud": "2.0.8",
+ "raw-body": "1.2.2",
+ "require-all": "^2.0.0",
+ "topcoat-fonts": "^0.2.4",
+ "type-is": "1.1.0",
+ "underscore": "^1.8.2",
+ "validate.io": "^1.5.0",
+ "xml-mapping": "~1.6.1"
},
"devDependencies": {
"mocha": "^2.1.0",