Comparing version 0.1.4 to 0.1.5
@@ -325,3 +325,3 @@ /** | ||
catch (err) { | ||
console.warn('Public folder does not exist: ' + pubFolderAbs); | ||
console.warn('Public folder does not exist: ' + pubFolderRel); | ||
return | ||
@@ -328,0 +328,0 @@ } |
@@ -39,2 +39,8 @@ /** | ||
Public: { | ||
/** | ||
* Do it all over: Kill current instance and try again. | ||
* This is effectively a page refresh for browsers but possibly | ||
* needs some extra work so it works for webworkers | ||
* or other environments. | ||
*/ | ||
refresh: function() { } | ||
@@ -54,2 +60,15 @@ } | ||
return 'ComponentBootstrapImpl_'; | ||
}, | ||
getCurrentBootstrapperImpl: function() { | ||
return Shared.Libs[this.getImplComponentLibName()]; | ||
}, | ||
Private: { | ||
/** | ||
* Get the current instance of the bootstrapper implementation. | ||
*/ | ||
getCurrentBootstrapperImpl: function() { | ||
return this.Instance.Libs[this.Shared.getImplComponentLibName()]; | ||
}, | ||
} | ||
@@ -103,6 +122,2 @@ }}), | ||
getCurrentBootstrapperImpl: function() { | ||
return Shared.Libs[this.getImplComponentLibName()]; | ||
}, | ||
// ############################################################################################################################### | ||
@@ -279,2 +294,6 @@ // Tools for bootstrapper implementations | ||
}, | ||
refresh: function() { | ||
this.getCurrentBootstrapperImpl().client.refresh(); | ||
} | ||
}, | ||
@@ -350,2 +369,6 @@ | ||
Instance.Libs.CommandProxy.execHostCommands(bootstrapData.componentCmds); | ||
}, | ||
refresh: function() { | ||
this.getCurrentBootstrapperImpl().refresh(); | ||
} | ||
@@ -413,3 +436,3 @@ }, | ||
// can properly set some initial client-side stuff | ||
setTimeout(function() { | ||
//setTimeout(function() { | ||
// call `onNewComponent` | ||
@@ -445,3 +468,3 @@ Instance.forEachComponentOfAnyType(function(component) { | ||
} | ||
}, 1); | ||
//}, 1); | ||
} | ||
@@ -448,0 +471,0 @@ } |
@@ -9,7 +9,11 @@ /** | ||
// squishy gives us some misc utilities | ||
require('squishy'); | ||
var StacktraceBuilder = squishy.Stacktrace; | ||
var CodeBuilder = squishy.CodeBuilder; | ||
var path = require('path'); | ||
// TODO: Move this out of here | ||
@@ -99,3 +103,2 @@ var Dev = 1; | ||
squishy.assert(typeof(factory) === 'function', 'ComponentDef.defBase must be called on a factory function that returns the `Base` component prototype.'); | ||
return new HostCore.FactoryDef(factory, 'Base', creationFrame, 'defBase'); | ||
@@ -117,3 +120,6 @@ }, | ||
defHost: function(factory) { | ||
return new HostCore.FactoryDef(factory, 'Host'); | ||
var trace = StacktraceBuilder.getStacktrace(); | ||
var creationFrame = trace[1]; | ||
return new HostCore.FactoryDef(factory, 'Host', creationFrame, 'defHost'); | ||
} | ||
@@ -469,6 +475,10 @@ }; | ||
*/ | ||
var installComponentOnHost = function(componentDef, clientDefs, map) { | ||
var installComponentOnHost = function(componentDef, clientDefs, map, creationFrame) { | ||
// get all command names and make them ready | ||
fixComponentDefinition(componentDef, 'Host'); | ||
fixComponentDefinition(componentDef, 'Client'); | ||
// add `File` and `Folder` to `Host` def | ||
componentDef.Host.File = creationFrame.fileName; | ||
componentDef.Host.Folder = path.dirname(creationFrame.fileName); | ||
@@ -564,11 +574,8 @@ // let each endpoint know all commands of the other side | ||
var creationFrame; | ||
if (def.Client instanceof HostCore.FactoryDef) { | ||
creationFrame = def.Client.creationFrame; | ||
} | ||
else { | ||
var trace = StacktraceBuilder.getStacktrace(); | ||
creationFrame = trace[defStackDepth || 1]; | ||
} | ||
console.assert(def.Client || def.Base || def.Host, | ||
'Component definition is invalid. It must at least define one property named `Host`, `Client` or `Base`.'); | ||
creationFrame = (def.Host || def.Client || def.Base).creationFrame; | ||
if (!name) { | ||
if (!name && creationFrame) { | ||
// deduct component name from filename | ||
@@ -588,10 +595,6 @@ name = creationFrame.fileName; | ||
// sanity checks: | ||
// validate name (depends on client definition) | ||
squishy.assert(name, 'Unnamed component is illegal. Make sure to add a `Name` property to your component.'); | ||
// get & validate Client definition | ||
var clientDef = def.Client; | ||
squishy.assert(!clientDef || (clientDef instanceof HostCore.FactoryDef && clientDef.type === 'Client'), | ||
'Component definition `' + fullName + '` did not have a client factory. Make sure that the `Client` property is declared with `ComponentDef.defClient`.'); | ||
console.assert(!clientDef || (clientDef instanceof HostCore.FactoryDef && clientDef.type === 'Client'), | ||
'Component definition has an invalid `Client` factory. Make sure that the `Client` property is declared with `Def.defClient(...)`.'); | ||
@@ -601,11 +604,16 @@ | ||
var baseDef = def.Base; | ||
squishy.assert(!baseDef || (baseDef instanceof HostCore.FactoryDef && baseDef.type === 'Base'), | ||
'Component definition `' + fullName + '` declared invalid `Base` factory. Make sure that the `Base` property is declared with `ComponentDef.defBase`.'); | ||
console.assert(!baseDef || (baseDef instanceof HostCore.FactoryDef && baseDef.type === 'Base'), | ||
'Component definition has an invalid `Base` factory. Make sure that the `Base` property is declared with `Def.defBase(...)`.'); | ||
// get & validate Host definition | ||
var hostDef = def.Host; | ||
squishy.assert(!hostDef || (hostDef instanceof HostCore.FactoryDef && hostDef.type === 'Host'), | ||
'Component definition `' + fullName + '` declared invalid `Host` factory. Make sure that the `Host` property is declared with `ComponentDef.defHost`.'); | ||
console.assert(!hostDef || (hostDef instanceof HostCore.FactoryDef && hostDef.type === 'Host'), | ||
'Component definition has an invalid `Host` factory. Make sure that the `Host` property is declared with `Def.defHost(...)`.'); | ||
return installComponentOnHost(def, defs || clientDefs.Components, map || Shared); | ||
// sanity checks: | ||
// validate name (depends on client definition) | ||
console.assert(name, 'Unnamed component is illegal. Make sure to add a `Name` property to your component.'); | ||
console.assert(creationFrame, 'INTERNAL ERROR: `creationFrame` was not defined when it should have been.'); | ||
return installComponentOnHost(def, defs || clientDefs.Components, map || Shared, creationFrame); | ||
}, | ||
@@ -612,0 +620,0 @@ |
@@ -30,3 +30,3 @@ /** | ||
var ComponentLoader = ComponentDef.lib({ | ||
Base: ComponentDef.defBase(function(Tools, Shared) { | ||
Base: ComponentDef.defBase(function(SharedTools, Shared) { | ||
@@ -39,3 +39,3 @@ return { | ||
Host: ComponentDef.defHost(function(Tools, Shared) { | ||
Host: ComponentDef.defHost(function(SharedTools, Shared) { | ||
@@ -51,2 +51,4 @@ return { | ||
// store config | ||
this.cfg = cfg; | ||
@@ -72,18 +74,14 @@ // set default configuration | ||
// TODO: We don't really need this | ||
// load all components from user-given folder | ||
for (var i = 0; i < cfg.files.length; ++i) { | ||
var fname = cfg.files[i]; | ||
// fix the file ending | ||
if (!fname.endsWith('.js')) fname += '.js'; | ||
var fPathAbs = path.join(componentFolder, fname); | ||
// all we need to do is to require the file and its containing components automatically register with ComponentDef | ||
var component = require(fPathAbs); | ||
// get file name | ||
component._def.File = fPathAbs; | ||
component._def.Folder = path.dirname(fPathAbs); | ||
// load all explicitely requested components from user-given folder | ||
if (cfg.files) { | ||
for (var i = 0; i < cfg.files.length; ++i) { | ||
var fname = cfg.files[i]; | ||
// fix the file ending | ||
if (!fname.endsWith('.js')) fname += '.js'; | ||
var fPathAbs = path.join(componentFolder, fname); | ||
// all we need to do is to require the file and its containing components automatically register with ComponentDef | ||
require(fPathAbs); | ||
} | ||
} | ||
@@ -102,2 +100,12 @@ | ||
return Shared; | ||
}, | ||
Private: { | ||
onNewClient: function() { | ||
var cfg = this.Shared.cfg; | ||
if (!cfg.lazyLoad) { | ||
// by default, automatically deploy all components to the client: | ||
this.Tools.requestClientComponents(Object.keys(this.Instance)); | ||
} | ||
} | ||
} | ||
@@ -104,0 +112,0 @@ }; |
@@ -23,3 +23,3 @@ /** | ||
if (!(componentNames instanceof Array)) { | ||
componentNames = Array.prototype.slice.call(arguments, 0); // convert arguments set to array | ||
componentNames = Array.prototype.slice.call(arguments, 0); // convert arguments to array | ||
} | ||
@@ -29,2 +29,6 @@ Instance.Libs.ComponentBootstrap.requestClientComponentsFromHost(componentNames); | ||
/** | ||
* Keeps buffering even after the current call ended. | ||
* This is to signal the beginning of an asynchronous operation whose result is to be sent to the client. | ||
*/ | ||
keepOpen: function() { | ||
@@ -36,8 +40,14 @@ Instance.Libs.ComponentCommunications.keepOpen(); | ||
* Flushes the current buffer. | ||
* This is generally only needed for connections that are not always open, | ||
* and need to be explicitely kept open during and flushed after asynchronous transactions. | ||
* This is to signal the end of an asynchronous operation whose result has already been sent to the client. | ||
*/ | ||
flush: function() { | ||
Instance.Libs.ComponentCommunications.flush(); | ||
} | ||
}, | ||
/** | ||
* Tell client to refresh current page. | ||
*/ | ||
refresh: function() { | ||
Instance.Libs.ComponentBootstrap.refresh(); | ||
} | ||
}; | ||
@@ -62,2 +72,7 @@ }, | ||
Tools.requestClientComponents = Instance.Libs.ComponentBootstrap.requestClientComponents.bind(Instance.Libs.ComponentBootstrap); | ||
/** | ||
* Refresh current page. | ||
*/ | ||
Tools.refresh = Instance.Libs.ComponentBootstrap.refresh.bind(Instance.Libs.ComponentBootstrap); | ||
}, | ||
@@ -64,0 +79,0 @@ }, |
{ | ||
"name": "nogap", | ||
"version": "0.1.4", | ||
"version": "0.1.5", | ||
"author": { | ||
@@ -5,0 +5,0 @@ "name": "Dominik Seifert", |
249
README.md
NoGap | ||
============= | ||
The NoGap framework delivers RPC + asset management + some other good stuff for Host <-> Client communication. | ||
The NoGap framework delivers RPC + asset management + some other good stuff for enjoyable Host <-> Client architecture development. | ||
Have a look at the <a href="https://github.com/Domiii/NoGap/tree/master/testapp">Sample App</a> for reference. | ||
This module is called `No` `Gap` because it removes the typical gap that exists between | ||
host and client and that makes a client<->server architecture so cumbersome to develop. | ||
NoGap's primary use case is to develop rich client-side applications while alleviating the typical hassles of doing so. | ||
Have a look at the [Samples](samples) for reference. | ||
Installation | ||
============= | ||
* [Install Node](http://nodejs.org/download/) | ||
* Make sure to select `Add to PATH` during GUI-based installation. | ||
* Open a command line | ||
* On Windows: Press `Ctrl+R` -> Type `cmd` -> `Enter` | ||
* Run: `npm install nogap` | ||
* Done. | ||
[Samples](samples) | ||
============= | ||
## [HelloWorld](samples/HelloWorld) | ||
```js | ||
var NoGapDef = require('nogap').Def; | ||
module.exports = NoGapDef.component({ | ||
Client: NoGapDef.defHost(function(Tools, Instance, Context) { | ||
return { | ||
initClient: function() { | ||
document.body.innerHTML = 'Hello World!'; | ||
} | ||
}; | ||
}); | ||
}); | ||
``` | ||
### Concepts | ||
* Get the NoGap module's `Def` helper: `var NoGapDef = require('nogap').Def;` | ||
* Define a new component: `NoGapDef.component({ ... });` | ||
* Add a `Client` definition to the component: `Client: NoGapDef.defClient(function(Tools, Instance, Context) { ... })` | ||
* Add `initClient` method to `Client` | ||
### What is the trick? | ||
* The `Client` code is automatically deployed to the client | ||
* `initClient` is then automatically called on the client, right after installation | ||
## [TwoWayStreet](samples/TwoWayStreet)<a name="twowaystreet"></a> | ||
```js | ||
var NoGapDef = require('nogap').Def; | ||
NoGapDef.component({ | ||
Host: NoGapDef.defHost(function(SharedTools, Shared, SharedContext) { | ||
var iAttempt = 0; | ||
return { | ||
Public: { | ||
tellClientSomething: function() { | ||
this.client.showHostMessage('We have exchanged ' + ++iAttempt + ' messages.'); | ||
} | ||
} | ||
}; | ||
}), | ||
Client: NoGapDef.defClient(function(Tools, Instance, Context) { | ||
return { | ||
initClient: function() { | ||
window.clickMe = function() { | ||
document.body.innerHTML +='Button was clicked.<br />'; | ||
this.host.tellClientSomething(); | ||
}.bind(this); | ||
document.body.innerHTML += '<button onclick="window.clickMe();">Click Me!</button><br />'; | ||
}, | ||
Public: { | ||
showHostMessage: function(msg) { | ||
document.body.innerHTML +='Server said: ' + msg + '<br />'; | ||
} | ||
} | ||
}; | ||
}) | ||
}); | ||
``` | ||
### Concepts | ||
* Add a `Client` definition to the component: `Client: NoGapDef.defClient(function(Tools, Instance, Context) { ... })` | ||
* `Client.initClient` | ||
* Add a `Host` definition to the component: `Host: NoGapDef.defHost(function(SharedTools, Shared, SharedContext) { ... })` | ||
* `Host.Public` | ||
* `Client.Public` | ||
### What is the trick? | ||
* `this.host` gives us an object on which we can call `Public` methods on the host | ||
* For example, we can call `tellClientSomething` which is a method that was defined in `Host.Public` | ||
* Once the host receives our request, it calls `this.client.showHostMessage` | ||
* Note: | ||
* Client: `this.host` vs. | ||
* Host: `this.client` | ||
## [TwoWayStreetAsync](samples/TwoWayStreetAsync) | ||
Now that our code keeps growing and you are starting to get the picture, let us just focus on code snippets from now on. | ||
Imagine the server had to do an asynchronous operation in [`tellClientSomething`](#twowaystreet). | ||
For example, it needs to read a file, or get something from the database. | ||
```js | ||
tellClientSomething: function() { | ||
this.Tools.keepOpen(); | ||
// wait 500 milliseconds before replying | ||
setTimeout(function() { | ||
this.client.showHostMessage('We have exchanged ' + ++iAttempt + ' messages.'); | ||
this.Tools.flush(); | ||
}.bind(this), 500); | ||
} | ||
``` | ||
### New Concepts | ||
* We need to perform an asynchronous request whose result is to be sent to the other side: | ||
* In that case, first call `this.Tools.keepOpen()`, so the client connection will not be closed automatically | ||
* Once you sent everything to the client, call `this.Tools.flush()` | ||
## [CodeSharingWithBase](samples/CodeSharingWithBase) | ||
```js | ||
tellClientSomething: function() { | ||
this.Tools.keepOpen(); | ||
// wait 500 milliseconds before replying | ||
setTimeout(function() { | ||
this.client.showHostMessage('We have exchanged ' + ++iAttempt + ' messages.'); | ||
this.Tools.flush(); | ||
}.bind(this), 500); | ||
} | ||
``` | ||
### New Concepts | ||
* We need to perform an asynchronous request whose result is to be sent to the other side: | ||
* In that case, first call `this.Tools.keepOpen()`, so the client connection will not be closed automatically | ||
* Once you sent everything to the client, call `this.Tools.flush()` | ||
<a name="getting_started"></a>Getting Started | ||
============= | ||
This tutorial is aimed at those who are new to `NoGap`, and new to `Node` in general. | ||
It should help you bridge the gap from the [Code Snippets](#code_snippets) to a real-world application. | ||
## Recommended File Structure | ||
. | ||
+-- components/ | ||
+-- lib/ | ||
+-- pub/ | ||
+-- package.json | ||
+-- appConfig.js | ||
+-- app.js | ||
Let's have a look at the different files and folders: | ||
### package.json | ||
This is the standard `Node` configuration file. Here you can declare your app's basic metadata and, most importantly, your dependencies. | ||
If you need one of the thousands over thousands of publicly available `Node` modules, two steps are required: | ||
1. add their name and your preferred version to `dependencies` | ||
2. Run `npm install` | ||
Done. Now the new module is available in your code via: | ||
`var someModule = require('some-module');` | ||
where `some-module` is the name you gave it in the package.json file. | ||
Check out <a href="https://www.npmjs.org/">https://www.npmjs.org/</a> to see all available modules. | ||
### `components/` | ||
This folder contains your `NoGap` components, and possibly (some of) their assets. You can name it anything you want. | ||
NOTE: Placing assets (such as *.html templates, stylesheets, images etc.) next to code is actually good style, if it supports modularization. | ||
If your components are mostly self-contained, you can easily move their whole folder, including their assets, to deploy them in other places. | ||
### `appConfig.js` | ||
This is your custom configuration file. You can name it anything you want. | ||
It contains some basic constant data that your application needs, such as database login and other setup information. | ||
The following is an example of a `NoGap` configuration. It requires at least three entries: | ||
* `baseFolder` | ||
* This is the folder, relative to your application (e.g. `app.js`) where you defined all NoGap components. | ||
* `publicFolder` | ||
* The folder to find all client asset files that cannot be found relative to a component. | ||
* Usually this is used to store client-only and shared javascript libraries that do not have `NoGap` support. | ||
* `files` | ||
* The actual component files (sans ".js"). Whenever you add a component, don't forget to list it here! | ||
```js | ||
"nogap": { | ||
"baseFolder" : "components", | ||
"publicFolder" : "pub", | ||
"files" : [ | ||
// list all components here: | ||
// utilities | ||
"ValidationUtil", | ||
// pages for guests | ||
"Guest", | ||
// pages for users | ||
"Main", | ||
"Home" | ||
] | ||
} | ||
``` | ||
There are more, optional parameters. Documentation will come soon. | ||
### `app.js` | ||
This defines your actual application. You can name it anything you want. Usually this file only does two things: | ||
1. Setup your app | ||
2. Start your <a href="http://expressjs.com/4x/api.html">`express` server</a> | ||
Express is the standard Node way of starting a web server and let clients connect. | ||
Once it is running you can connect to it with your browser on the specified port. | ||
With `NoGap`, we add one more job to it: | ||
1. Setup your app | ||
2. Initialize `NoGap` | ||
3. Start your <a href="http://expressjs.com/4x/api.html">`express` server</a> |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
312123
40
4125
252