Rain Documentation | |
| ../lib/cache.js |
Caching module. Works in-node-memory (simple JavaScript object) and using redis.
|
var mod_promise = require('promised-io')
, mod_resources = require('./resources.js')
, mod_redback = null
, redbackClient = null
, config = null
, logger = require('./logger.js').getLogger('Cache', require('./logger.js').Logger.SEVERITY.ERROR)
, cache = null
function configure(c) {
config = c;
if (config && config.caching && config.caching.resources) {
logger.debug('activate caching');
mod_redback = require('redback');
redbackClient = mod_redback.createClient();
cache = redbackClient.createCache('rain');
}
}
function toCache(url, data) {
if (cache) {
logger.debug('write to cache; url: ' + url);
cache.set(url, data, config.caching.ttl, function () {
});
}
}
function fromCache(url) {
var defer = mod_promise.defer()
, cr
if (cache) {
cache.exists(url, function (err, exists) {
if (err) {
throw new Error(err.toString());
defer.resolve(null);
}
if (exists) {
cache.get(url, function (err, value) {
if (err) {
throw new Error(err);
} else {
logger.debug('got from cache; url: ' + url);
cr = new mod_resources.Resource(url, value);
defer.resolve(cr);
}
});
} else {
logger.debug('not found in cache; url: ' + url);
defer.resolve(null);
}
});
} else {
defer.resolve(null);
}
return defer;
}
exports.fromCache = fromCache;
exports.toCache = toCache;
exports.configure = configure;
|
| ../lib/cssnormalizer.js |
This module normalizes URLs in CSS files provided by modules to absolute paths
that are valid on an individual module host.
|
var mod_path = require('path')
mod_assert = require('assert')
, c = console.log
|
Normalizes a URL in a CSS file. Does not change fully-qualified http:// URLs.
param: String data CSS data param: String path root path param: String orighost URL of old source host param: String newhost URL of new source host return: String normalized CSS data publi: c
|
function normalize(data, path, orighost, newhost) {
var data = data.replace(/url\(['"]?([^'"\)']+)['"]?\)?/mg, function () {
var p;
if (arguments[1].indexOf('http://') === 0) { return arguments[1]; }
p = mod_path.join(path, arguments[1]);
if (orighost !== newhost) { p = newhost + p; }
p = 'url(\"' + p + '\")';
return p;
});
return data;
}
exports.normalize = normalize;
|
| ../lib/logger.js |
Logger module.
Has appenders for console and HTTP.
|
function getLogger (name, level) {
var l = typeof level !== 'undefined' ? level : Logger.SEVERITY.INFO
return new Logger(name, l);
}
function Logger(name, level) {
if ('undefined' === typeof name) { name = '' }
this.appender = [ new ConsoleAppender() ];
this.name = name;
this.level = level;
}
Logger.SEVERITY = {
INFO : 5,
DEBUG : 4,
WARN : 3,
ERROR : 2
}
Logger.prototype.info = function (msg) {
this._log(msg, Logger.SEVERITY.INFO);
}
Logger.prototype.debug = function (msg) {
this._log(msg, Logger.SEVERITY.DEBUG);
}
Logger.prototype.warn = function (msg) {
this._log(msg, Logger.SEVERITY.WARN);
}
Logger.prototype.error = function (msg) {
this._log(msg, Logger.SEVERITY.ERROR);
}
Logger.prototype._log = function (msg, level) {
if (level > this.level) { return; }
for (var i = this.appender.length; --i >= 0;) {
this.appender[i].append(msg, this.name, level);
}
}
function ConsoleAppender () {
}
ConsoleAppender.prototype.append = function (msg, logger, level) {
var col = level == 2 ? '\033[31m' : '\033[32m',
msg = logger != '' ? (col + logger + ':\033[39m ' + msg ) : msg
var lvls = {'5':'info', '4':'info', '3':'warn', '2' :'error'};
console[lvls[level]](msg);
}
function HttpLogHostAppender (url) {
var opts = parseUri(url);
this.options = {
host: opts.host,
port: opts.port,
path: opts.path,
method: 'POST',
headers : {
"Content-Type" : "application/json"
}
};
}
HttpLogHostAppender.prototype.append = function (msg, logger, level) {
var req = http.request(this.options, function(res) {
res.setEncoding('utf8');
res.on('data', function (chunk) {});
});
var data = {};
data.msg = msg;
data.origin = logger;
data.timestamp = new Date().getTime();
try {
req.write(JSON.stringify(data));
} catch (error) {
}
req.end();
}
exports.getLogger = getLogger;
exports.Logger = Logger;
|
| ../lib/modules.js |
|
var mod_sys = require('sys')
, mod_path = require('path')
, mod_fs = require('fs')
, mod_promise = require('promised-io')
, mod_jsdom = require('jsdom')
, mod_url = require('url')
, mod_querystring = require('querystring')
, mod_renderer = require('./renderer.js')
, mod_resourcemanager = require('./resourcemanager.js')
, tagmanager = null
, config = null
, taglibrary = null
, moduleRootFolder = mod_path.join(__dirname, '..', 'modules')
, logger = require('./logger.js').getLogger('Modules')
function configure(c, t) {
config = c;
tagmanager = t;
}
function handleControllerRequest (req, res, next) {
logger.debug('handleControllerRequest ' + req.url);
var modulename = req.params[0]
, method = req.params[1]
, mp = mod_path.join(moduleRootFolder, 'modules', modulename, 'main.js')
mod_path.exists(mp, function (exists) {
if (exists) {
var module = require(mp);
if (module[method]) {
module[method](req, res).then(function (ret) {
res.end(ret.data);
});
} else {
res.writeHead(404, { 'Content-Type' : 'text/plain'} );
res.end('method not available');
}
} else {
res.writeHead(404, { 'Content-Type' : 'text/plain'} );
res.end('unknown module ' + modulename);
}
});
}
function handleScriptRequest (req, res, next) {
var modulename = req.params[0]
, path = req.params[1]
, mp = 'file://' + mod_path.join(moduleRootFolder, modulename, 'htdocs', path);
logger.debug('handleScriptRequest ' + mp);
res.setHeader('Content-Type', 'text/javascript');
mod_resourcemanager.getResource(mp).then(function (resource) {
res.end(resource.data.toString());
});
}
function handleViewRequest (req, res, next) {
var query = mod_querystring.parse(mod_url.parse(req.url).query)
, mode = query.type
, baseurl = req.url.substring(0, req.url.lastIndexOf('/'))
, modulename = req.params[0]
, viewname = req.params[1]
logger.debug('handleViewRequest, module: ' + req.params[0] + ', view: ' + req.params[1]);
var r = new mod_renderer.Renderer(tagmanager);
console.time('render')
r.render(getViewUrl(modulename, viewname), mode).then(function (doc) {
console.timeEnd('render');
res.end(doc);
});
}
const DEFAULT_VIEW = 'main.html';
|
Gets an absolute file URL of a module.
|
function getViewUrl(moduleid, viewname) {
return 'file://' + require('path')
.join(__dirname
, '..'
, 'modules'
, moduleid
, 'htdocs'
, viewname ? viewname : DEFAULT_VIEW
);
}
function getModule (modulename) {
return true;
}
exports.handleControllerRequest = handleControllerRequest;
exports.handleScriptRequest = handleScriptRequest;
exports.handleViewRequest = handleViewRequest;
exports.configure = configure;
exports.getViewUrl = getViewUrl;
exports.getModule = getModule;
|
| ../lib/redisclient.js |
Manages the connection to redis db.
|
var redback = require('redback')
, redbackClient = redback.createClient()
console.log('redis client started');
var channel = redbackClient.createChannel(redbackClient);
channel.publish('foobar', function () {
console.log('published');
});
channel.subscribe('foobar', function (data) {
console.log('received ' + data)
})
channel.on('message', function (msg) {
console.log('message!! ' + msg);
});
|
| ../lib/renderer.js |
Here da magic.
|
var mod_xml = require('node-xml')
, mod_promise = require('promised-io')
, sys = require('sys')
, logger = require('./logger.js').getLogger('TagManager')
, mod_tagmanager = require('./tagmanager.js')
, mod_resourcemanager = require('./resourcemanager.js')
, mod_resources = require('./resources.js')
, mod_path = require('path')
, mod_modules = require('./modules.js')
, c = console.log
, i = sys.inspect
, mr = function () { return Math.random()*0; }
function Renderer(tagmgr) {
if (!tagmgr) { throw new Error('tagmanager required'); }
this.tagmanager = tagmgr
this.renderResources = [];
}
|
The main workhorse function.
|
Renderer.prototype.render = function (url, mode) {
var self = this;
return this._render(url).then(function (renderres) {
c(self.renderResources.length);
var deps = self.calcDependencies(renderres)
, out = self[mode!=='json' ? 'outputHtml' : 'outputJson'](renderres, deps.css, deps.scripts, deps.locales);
return out;
} );
}
|
|
Renderer.prototype.calcDependencies = function (renderres) {
var self = this
, css = []
, scripts = []
, locales = []
, regex = new RegExp('file://' + mod_path.join(__dirname, '..'));
this.renderResources.reverse().forEach(function (dep) {
c('................... ' + dep.url)
path = dep.url.replace(regex, "")
.replace(/htdocs.*$/, '');
Array.prototype.push.apply(css, dep.cssdeps.map(function (item) {
return mod_path.join(path, item);
}));
Array.prototype.push.apply(scripts, dep.scriptdeps.map(function (item) {
return mod_path.join(path, item);
}));
Array.prototype.push.apply(locales, dep.localedeps.map(function (item) {
return mod_path.join(path, 'i18n', item);
}));
});
this.renderResources.reverse().forEach(function (dep) {
c(dep.url);
});
return { 'css' : css, 'scripts' : scripts, 'locales' : locales };
}
const RESOURCE_LOADER_URLPATH = "/resources?files";
|
Transforms a RenderResource into HTML output format.
This includes all dependencies of a view that are rendered into the output by using
require.js function calls to have additional dependencies loaded by the web client.
param: Resource resource RenderedViewResource param: String[] css URL to CSS dependency param: String[] scripts URL to JavaScript dependency param: String[] locales URL to locale dependency
|
Renderer.prototype.outputHtml = function (resource, css, scripts, locales) {
var doc = resource.data.toString();
var markup = [];
if (css && css.length) {
markup.push('<link rel="stylesheet" type="text/css" href="'
, RESOURCE_LOADER_URLPATH, '=',css.join(';'), '"/>\n');
}
if (scripts && scripts.length) {
markup.push('<script type="application/javascript" src="', RESOURCE_LOADER_URLPATH, '='
, scripts.join(';'), '"></script>\n');
}
locales.forEach(function (locale) {
c('l ' + locale);
});
if (resource.elements.length > 0) {
markup.push('\n<script type="application/javascript">\n');
resource.elements.forEach(function (elem) {
var im = elem.instanceId ? ',"text!/instances/' + elem.instanceId + '.js"' : '';
var l = elem.locale
markup.push('\nrequire(["/modules/', elem.moduleId, '/client.js"'
, ', "text!/modules/', elem.moduleId, '/main.html?type=json"'
, im
, ', "text!/modules/', elem.moduleId, '/locales/de_DE.xml"'
, '], '
, ' function (module, template, instance, localefile) { module.initView("'
, elem.id, '", template, instance, localefile) } );'
);
});
markup.push('\n</script>\n');
}
var idx = doc.indexOf('</head>');
doc = doc.substring(0, idx) + markup.join('') + doc.substring(idx, doc.length);
return doc;
}
|
Renders a resource as JSON. Currently mixes up partials and JSON output, which should not be the same.
param: RenderedViewResource resource Resource to render param: String[] css URL to CSS dependency param: String[] scripts URL to JavaScript dependency param: String[] locales URL to locale dependency
|
Renderer.prototype.outputJson = function (resource, css, scripts, locales) {
var body = resource.data.toString().match(/(<body[^>]*>)([\s\S]*)(<\/body>)/mi)[2];
var obj = {
"resources" : {
"css" : css
, "script" : scripts
, "locales" : locales
}
, "content" : body
};
return JSON.stringify(obj);
}
Renderer.prototype._render = function (url) {
var p = new mod_promise.Promise()
, self = this
c('render ' + url);
self.loadResource(url).then(function (resource){
if (!resource instanceof mod_resources.Resource) { throw new Error('wrong type'); }
self.parse(resource).then(function (renderres) {
if (!renderres instanceof mod_resources.RenderedViewResource) { throw new Error('wrong type'); }
self.renderResources.push(renderres);
c('pushing ' + renderres.elements)
var r = []
, depsdone = {};
if (renderres && renderres.elements) {
renderres.elements.forEach(function (item) {
debugger;
if (mod_modules.getModule(item.moduleId)) {
var viewurl = mod_modules.getViewUrl(item.moduleId, 'main.html');
if (!depsdone[viewurl]) {
r.push(self._render(viewurl));
} else { }
depsdone[viewurl] = true;
} else {
throw new Error('module not found');
}
});
mod_promise.all(r).then(function (vals) {
c('resolved all sub-renders ' + url);
p.resolve(renderres);
});
} else {
p.resolve(renderres);
}
});
});
return p;
}
Renderer.prototype.parse = function (resource) {
return parseHtmlView(resource, this.tagmanager);
}
Renderer.prototype.loadResource = function (url) {
return mod_resourcemanager.getResource(url);
}
exports.Renderer = Renderer;
function parseHtmlView (resource, tagmanager) {
var defer = new mod_promise.defer()
, url = resource.url
, parser = new mod_xml.SaxParser(new DocParser())
, elements = []
, outputBuffer = []
, cssresources = []
, scriptresources = []
, textresources = []
parser.parseString(resource.data.toString());
return defer.promise;
function DocParser() {
var tagRemoved = false;
return function (cb) {
cb.onStartDocument(function() {
});
cb.onEndDocument(function() {
console.log(elements);
defer.resolve(new mod_resources.RenderedViewResource(url
, new Buffer(outputBuffer.join(''))
, elements
, cssresources
, scriptresources
, textresources)
);
});
cb.onStartElementNS(function(elem, attrs, prefix, uri, namespaces) {
var instanceid
, viewname
if (elem === 'link') {
attrs.forEach(function (item) {
if (item[0] === 'href') {
cssresources.push(item[1]);
tagRemoved = true;
}
});
} else if (elem == 'script') {
attrs.forEach(function (item) {
if (item[0] === 'src') {
scriptresources.push(item[1]);
tagRemoved = true;
}
});
} else if (elem == 'text' && prefix === 'tx') {
attrs.forEach(function (item) {
if (item[0] === 'key') {
textresources.push(item[1]);
}
});
} else {
var tag = tagmanager.getTag(elem, attrs, prefix, uri, namespaces);
if (tag !== null) {
for (var i = 0; i < attrs.length; i++) {
if (attrs[i][0] === 'instanceid') {
instanceid = attrs[i][1];
break;
}
if (attrs[i][0] === 'view') {
viewname = attrs[i][1];
break;
}
}
var eid = addId(attrs);
var elemResource = {
'id' : eid
, 'instanceId' : instanceid
, 'moduleId' : tag.module
, 'view' : viewname
};
elements.push(elemResource);
}
}
if (!tagRemoved) {
outputBuffer = outputBuffer.concat(copyStartTag(elem, attrs, prefix, uri, namespaces));
}
addId(attrs);
});
cb.onEndElementNS(function(elem, prefix, uri) {
if (!tagRemoved) {
outputBuffer.push("</", (prefix ? prefix + ":" : ""), elem, ">");
} else { }
tagRemoved = false;
});
cb.onCharacters(copy);
cb.onCdata(copy);
function copy(chars) {
outputBuffer.push(chars);
};
cb.onWarning(function(msg) {
logger.warn('warning ' + msg);
});
cb.onError(function(msg) {
logger.error('<ERROR>'+JSON.stringify(msg)+"</ERROR>");
});
}
}
}
|
add an 'id' attribute to identify the element when inserting its rendered content
|
function addId(attrs) {
var eid
, elemId = "module" + new Date().getTime() + "" + parseInt(Math.random()*10);
for (var j = 0, al = attrs.length, hasId = false; j < al; j++) {
if (attrs[j][0] === "id") {
eid = attrs[j][1];
hasId = true;
}
}
if (!hasId) {
attrs.push(["id", elemId]);
return elemId
} else {
return eid;
}
}
function copyStartTag(elem, attrs, prefix, uri, namespaces) {
var outputBuffer = [];
outputBuffer.push("<", (prefix ? prefix + ":" : ""), elem);
if (namespaces.length > 0 || attrs.length > 0) {
outputBuffer.push(" ");
}
for (var i = 0; i < attrs.length; i++) {
outputBuffer.push(attrs[i][0], '="', attrs[i][1], '"');
if (i < attrs.length - 1) {
outputBuffer.push(' ');
}
}
outputBuffer.push(['meta', 'link', 'br', 'img', 'input'].indexOf(elem) > -1 ? '/>' : '>');
return outputBuffer;
}
|
| ../lib/resourcemanager.js |
Resources are normally not constructed manually but accessed using this module.
If caching is enabled, the Resource Manager takes care of caching.
Todos
- Resource Resolution
- Define schemes and transitions:
- External HTTP URLs --> Routed URLS --> Module-internal URLs --> File system URLs; vice versa
- Module-specific URIs, e.g. module://weather;1.0.1 maps to [determined by global web component broker]
- Need clear borders between sub-components for all URL schemes
|
var resources = require('./resources.js')
, mod_events = require('events')
, mod_resources = require('./resources.js')
, mod_promise = require('promised-io')
, mod_path = require('path')
, mod_sys = require('sys')
, logger = require('./logger.js').getLogger('ResourceManager')
, config = null
, cache = null
, c = console.log
const documentRoot = mod_path.join(__dirname, '..');
function configure(c, ch) {
config = c;
cache = ch;
}
|
Convenience array style version of getResource
param: String[] urls URL to resource return: Promise Promise, is resolved once all resources are loaded publi: c
|
function getResources(urls) {
var defer = mod_promise.defer();
var self = this;
var u =
mod_promise.all(urls.map(function (item) {
return self.getResource(item);
}))
.then(function (all) {
defer.resolve(all);
});
return defer.promise;
}
|
Loads a resource from either a file://, http:// or relative path.
|
function getResource (url) {
return loadUrl(url);
}
|
Gets a resource either from the cache (if enabled) or loads it.
|
function loadUrl(url) {
var defer = mod_promise.defer()
, type = null
, resource = null
if (cache) {
cache.fromCache(url).then(function (resource) {
if (!resource) {
resource = getResourceByUrl(url)
.then(function (resource) {
cache.toCache(url, resource.data);
defer.resolve(resource);
});
} else {
defer.resolve(resource);
}
});
} else {
resource = getResourceByUrl(url)
.then(function (resource) {
defer.resolve(resource);
});
}
return defer.promise;
}
|
Gets the correct resource type assiocated to a URL without querying the cache.
|
function getResourceByUrl(url) {
var defer = mod_promise.defer()
, resource = null
, intUrl = url
if (url.indexOf('http://') === 0) {
resource = new mod_resources.HttpResource();
} else if (url.indexOf('file://') === 0) {
resource = new mod_resources.FileResource();
} else {
intUrl = translatePathToFileUrl(url);
resource = new mod_resources.FileResource();
}
resource.load(intUrl);
resource.addListener('stateChanged', function () {
defer.resolve(resource);
});
return defer.promise;
}
|
Map a standard file path to a absolute file:// URL. Paths are always assumed relative to an
installation root folder.
|
function translatePathToFileUrl(path) {
var p = 'file://'+ mod_path.join(documentRoot, path);
return p;
}
exports.getResources = getResources;
exports.getResource = getResource;
exports.configure = configure;
exports.translatePathToFileUrl = translatePathToFileUrl;
|
| ../lib/resources.js |
Everything is a resource.
Todos
- Resources should be sealed and/or frozen after loading (state 'complete')
|
var logger = require('./logger.js').getLogger('Resource')
, mod_util = require('util')
, mod_events = require('events')
, mod_http = require('http')
, mod_fs = require('fs')
, mod_url = require('url')
function uuid() {
return new Date().getTime() + '-' + Math.round(Math.random()*10000);
}
|
class: Base
resource class
## param: String URL of resource param: String resource payload
|
function Resource(url, data) {
this._state = Resource.STATE.INIT;
if (typeof url !== 'undefined' && typeof data !== 'undefined') {
this.url = url;
this.data = new Buffer(data);
this._state = Resource.STATE.READY;
} else {
this.data = null;
}
this.uuid = uuid();
};
mod_util.inherits(Resource, mod_events.EventEmitter);
Object.defineProperty(Resource.prototype, 'state', {
get : function () {
return this._state;
},
set : function (val) {
if ('number' !== typeof val) throw Error('number expected, got ' + val);
if (val !== this._state) {
this._state = val;
this.emit('stateChanged', this);
}
}
});
|
Add a resource as a dependency to this resource.
|
Resource.prototype.addDependency = function (res) {
this._requiredResources[res.uuid] = res;
var self = this;
res.addListener('stateChanged', function () {
self._onDepStateChange.call(self, res);
});
};
Resource.prototype._onDepStateChange = function (resource) {
var state = Resource.STATE.READY;
for (rn in this._requiredResources) {
state = Math.min(this._requiredResources[rn].state, state);
}
this.state = state;
};
|
Must be implemented by subclasses.
|
Resource.prototype.load = function () {
throw new Error('not implemented');
}
Resource.STATE = {
INIT : 0
, LOADING : 2
, WAITING : 4
, READY : 6
};
exports.Resource = Resource;
|
|
function FileResource() {
Resource.apply(this, arguments);
}
mod_util.inherits(FileResource, Resource);
|
|
FileResource.prototype.load = function (file) {
if ('undefined' === typeof file || file.indexOf('file://') !== 0) {
throw new Error('missing or faulty argument');
}
this.url = file;
this.state = Resource.STATE.LOADING;
var self = this,
file = file.substring(7);
mod_fs.readFile(file, function (err, data) {
if (err) { throw err; }
self.data = data;
self.state = Resource.STATE.READY;
});
};
|
|
function HttpResource() {
Resource.apply(this);
}
mod_util.inherits(HttpResource, Resource);
|
|
HttpResource.prototype.load = function (url) {
if ('undefined' === typeof url || url.indexOf('http://') !== 0) {
throw new Error('missing or faulty argument');
}
var self = this,
req;
this.url = url;
this.state = Resource.STATE.LOADING;
this.content = '';
this._options = mod_url.parse(url);
req = mod_http.get(this._options, function (res) {
if (res.statusCode >= 400 && res.statusCode <= 500) {
throw new Error('URL could not be loaded, code ' + res.statusCode
+ ', url ' + self._options.host + ':' + self._options.port + self._options.path);
}
res.on('data', function (data) {
self.data = data;
});
res.on('end', function () {
self.state = Resource.STATE.READY;
});
});
};
|
param: String url http URL of view template resource param: Buffer data param: String[] cssdeps param: String[] scriptdeps param: String[] localdeps
|
function RenderedViewResource(url, data, elements, cssdeps, scriptdeps, localedeps) {
Resource.call(this, url, data);
this.elements = elements;
this.cssdeps = cssdeps;
this.scriptdeps = scriptdeps;
this.localedeps = localedeps;
}
RenderedViewResource.prototype.toString = function () {
process.exit();
return this.url + ',' + this.cssdeps + '' + this.scriptdeps + ',' + this.localedeps;
}
mod_util.inherits(RenderedViewResource, Resource);
Resource.prototype.toString = function () {
process.exit();
}
exports.FileResource = FileResource;
exports.HttpResource = HttpResource;
exports.RenderedViewResource = RenderedViewResource;
|
| ../lib/server.js |
main server.
|
var mod_connect = require('connect')
, mod_sys = require('sys')
, mod_path = require('path')
, mod_resourceservice = null
, mod_resourcemanager = require('./resourcemanager.js')
, mod_tagmanager = require('./tagmanager.js')
, mod_cache = require('./Cache.js')
, mod_socketio = require('./socketio.js')
, mod_modules = require('./modules.js')
, mod_fs = require('fs')
, cache = null
, logger = require('./logger.js').getLogger('Server')
if (process.argv.length < 3) {
logger.error('usage: ...');
process.exit();
}
var config = null;
logger.info('reading config from ' + process.argv[2]);
mod_fs.readFile(process.argv[2], function (err, data) {
if (err) {
logger.error('error reading configuration');
process.exit();
}
config = JSON.parse(data);
logger.info('config loaded');
mod_cache.configure(config);
mod_resourcemanager.configure(config, mod_cache);
mod_resourceservice = require('./resourceservice.js')(config, mod_resourcemanager);
mod_tagmanager.setTagList(config.taglib);
mod_modules.configure(config, mod_tagmanager);
createServer(config);
});
function createServer(config) {
var server = mod_connect.createServer(
mod_connect.favicon()
, mod_connect.router(function (app) {
app.get(/^\/modules\/([^\.]*)\/(.*\.js)$/, mod_modules.handleScriptRequest);
app.get(/^\/modules\/([^\.]*)\/(.*\.html)$/, mod_modules.handleViewRequest);
app.get(/^\/modules\/([^\.]*)\/controller\/(.*)$/, mod_modules.handleControllerRequest);
app.get(/^\/resources(.*)$/, mod_resourceservice.handleRequest);
}
)
, mod_connect.static(config.server.documentRoot)
, mod_connect.logger()
);
server.listen(config.server.port);
}
|
| ../lib/resourceservice.js |
The resource service, code name "cyclone", is a center piece of Rain.
Clients can call it with a list of resources separated by semicolons,
that are loaded and concatenated in the order of their occurence.
Rain currently creates request markup to this service in the render process.
Examples
<link rel="stylesheet" type="text/css" href="/resources?files=/modules/scrollabletable/main.css
;/modules/domains/main.css;/modules/weather/main.css;/modules/app/application.css
;/modules/app/jquery-ui/css/smoothness/jquery-ui-1.8.14.custom.css"/>
<script type="application/javascript" src="/resources?files=/modules/app/require-jquery.js
;/modules/app/jquery-ui/js/jquery-ui-1.8.14.custom.min.js
;/modules/app/js/socket.io/socket.io.js"></script>
A file must follow some simple rules to be loadable by the resource service:
- For URLs, e.g. for background images, always use the url() notation, since the parsers won't recognize them otherwise.
Todos
|
module.exports = function (c, resmgr) {
var mod_fs = require('fs')
, mod_url = require('url')
, mod_http = require('http')
, mod_promise = require('promised-io')
, mod_path = require('path')
, mod_cssnormalizer = require('./cssnormalizer.js')
, logger = require('./logger.js').getLogger('ResourceService')
, config = c
, resourceManager = resmgr
if (!config || !resmgr) { throw new Error('dependencies missing'); }
|
* Handles resource requests, usually created by the render engine when scanning a template file.
* Resource requests are aggregated requests to CSS or JavaScript files, both can not be mixed
* into a single request. The correct mime type is determined by the suffix of the first
* file.
*
* @param {request} req Request object
* @param {response} res Response object
* @param {response} next Next connect middleware routing rule
* @public
|
function handleRequest (req, res, next) {
var url = mod_url.parse(req.url, true)
, files = url.query.files.indexOf(';') === -1
? [url.query.files]
: url.query.files.split(';')
, isCss = files[0].lastIndexOf('.css') == files[0].length - 4
, absFiles;
if (!config || !resourceManager) {
logger.error('config not found');
return;
}
files = files.map(function (url) {
return config.server.serverRoot + url.replace(new RegExp("^(/modules\/[^\/]+)"), "$1/htdocs/")
})
resourceManager.getResources(files).then(function (resources) {
var output = [];
resources.forEach(function (resource) {
output.push('\n\n\n\/\*\* FILE ', resource.url, ' */\n', resource.data.toString());
});
res.end(output.join(''));
});
}
return {
'handleRequest' : handleRequest
}
};
|
| ../lib/socketio.js |
This does not work yet as the connection is lost on re-loading the server.
Client reload should go into the 'demon' script (to be build from the ashes of run.js)
|
function init (io) {
io.sockets.on('connection', function (socket) {
console.log('client connected');
socket.on('rain.application.state', function (from, msg) {
console.log('rain.application.state message', from, ':', msg);
});
socket.on('disconnect', function () {
io.sockets.emit('user disconnected');
});
});
}
function send (io) {
console.log('send reload request');
io.sockets.emit('rain.application.reload', { 'reload' : true } );
}
exports.init = init;
|
| ../lib/tagmanager.js |
The Tag Manager handles the mappings between CSS selectors on view templates and web components.
Todos
- Make it dynamicly configurable
|
var logger = require('./logger.js').getLogger('TagManager');
function TagManager() {
var taglist = [];
this.addTag = function (tag) {
taglist.push(tag);
}
this.setTagList = function (tags) {
taglist = tags
}
|
[TBD] This code sucks big time...
I need to organize tags more efficiently to get rid of this ugly and buggy piece of code.
Sizzle could be used, but it's most probably way too slow.
Todos
- find best match, selectors with predicated must be ranked higher
- more than one module my map to a single element
|
this.getTag = function (elem, attrs, prefix, uri, namespaces) {
for (var i = 0, tag = null; i < taglist.length; i++) {
tag = taglist[i];
if (uri !== null && tag.namespace != '' && tag.namespace != uri) {
continue;
}
var si = tag.selector.indexOf('[');
if (si !== -1) {
var tagelem = tag.selector.substring(0, si),
preds = {},
ps,
attrh = {};
ps = tag.selector.substring(si).split(']').forEach(function (val) {
if (val === '') return;
preds[val.substring(1, val.indexOf('='))] = val.substring(val.indexOf('=') + 1);
});
for (var j = 0; j < attrs.length; j++) {
attrh[attrs[j][0]] = attrs[j][1];
}
} else {
var tagelem = tag.selector;
}
var fe = true;
if (tagelem !== elem && tagelem !== '*') {
fe = false;
continue;
}
var fp = true
if (si !== -1) {
var count = 0;
for (pred in preds) {
if (attrh[pred] !== preds[pred]) {
fp = false;
break;
}
count++;
}
if (count -1 >= j) {
fp = false;
break;
}
}
if (!fp) {
continue;
}
if (!fe) {
continue;
}
return tag;
}
return null;
}
}
module.exports = new TagManager();
|
| ../lib/modulecontainer.js |
Module Container, Manager, Factory, whatever. All module related thingz must belong to us.
Todos
|
var mod_fs = require('fs')
, mod_resourcemanager = require('./resourcemanager.js')
, path = require('path')
function moduleRootFolder() {
return path.join(__dirname, '..', 'modules');
}
function scanFolder(folder) {
mod_fs.readdir(moduleRootFolder(), function (err, files) {
if (err) throw err;
mod_resourcemanager.getResources(['file://' + moduleRootFolder() + '/app/htdocs/index.html']).then(function () {
console.log('grunz...');
});
});
}
scanFolder();
|