JumboJS

Introduction

About JumboJS

JumboJS is fast modern enterprise level MVC framework for Node.js which focuses on object-oriented programming, Inversion of Control and Separation of Concerns.

Main Features of JumboJS

  • No require hell! Global lazy-loading namespace exists,
  • native async & await support,
  • integrated Node.js clustering - multi core/CPU support,
  • advanced dynamic routing system (just one route enough for most apps),
  • Inversion of Control - constructor Dependency Injection,
  • code can be changed while runtime,
  • unexpected errors are catched and logged, then process is restarted,
  • client-side micro framework automatically creating SPA without any client-side programming,
  • fully configurable logging with log levels,
  • subdomains! More "modules" in one application accessible via subdomains,
  • code-first ORM/ODM with migrations (UniMapperJS),
  • automatic sessions,
  • memory and disk cache,
  • integrated email sender (soon),
  • high performance - 3 900 requests per second with one worker (2,33 GHz core),
  • low dependecy,
  • integrated globalization,
  • and more...

Framework structure

In the picture below you can see basic framework structure and data flow. Some things are abstracted and joined to keep diagram clear.

Getting Started

Node.js and Npm

JumboJS is built upon Node.js so you need Node.js in version 8.10.x or higher and its package manager npm.

Node.jsĀ® is a JavaScript runtime built on Chrome's V8 JavaScript engine. Node.js uses an event-driven, non-blocking I/O model that makes it lightweight and efficient. Node.js' package ecosystem, npm, is the largest ecosystem of open source libraries in the world. (taken from nodejs.org)

Installation

The best way how to start with JumboJS is to install jumbo-developer package and use it to create new project. So first install jumbo-developer via npm npm install jumbo-developer -g.

Now you can create your first project. Enable jumbo-developer, type jumbo-developer to console/terminal and developer tool will start. First you must specify your project location, it'll do nothing, it just save your location and all future actions will be processed over that location. Then you should finally create your project. Type create project, and your first project is going to be created in directory you specified in previous step.

Now you should run your application. Go to your project directory and run it via npm start. Your application should run on address http://127.0.0.1 on port 30000. If you want to change default port, edit app.js file, and change parameter of runWhenReady(port, ...).

If you want to use framework's subdomains, you should edit your local hosts file and add some domain for your application.

Project Structure

    - adapters # Adapters for components
    - app # Your main folder
        - controllers # Place controllers here
        - facades # Place facades here
        - models # Place models here
        - services # Place services here
        - sub-apps # Folder for subapps which will be accessible via subdomains
            - Example # Subdomain name
                - controllers
                - models
                - ...
        - templates # Place view templates here
					
    - bootstrap # Folder for init scripts (registering routes, dependency, etc.)
    - data
        - errors # Error HTML pages
        - logs # Logs are saved here
        - uploads # All uploaded files are placed here
    - public # Everything inside is accessible at /public/
        - images
        - scripts
        - styles
    - temp
        - cache # Cached templates
        - session # Stored session data
    - app.js # Main script
    - config.js # App configuration
            

Autoloaded Namespaces

In JumboJS there is no need to use requires/imports with complicated relative paths (require-hell). Yes, you must require Node.js modules or your custom 3rd party stuff but framework classes are autoloaded to global namespaces.

There are global Objects Jumbo and App. These Objects contain all classes from framework which are autoloaded when you start app. These Objects create namespaces and structure of namespaces is copied from folder structure. Namespace Jumbo point to framework core. Namespace App points to folder /app/. Eg. /app/controllers/HomeController.js file is accessible via App.Controllers.HomeController.

Classes in namespaces are defined as getters and are required just in time so global namespaces are lazy, same as calling require(). Inside getters there is require() call so after first get it's cached thanks to require cache.

Configuration

For basic configuration there is file /config.js. It's JS file so you can use IDE's completition. And you can add custom properties into config object too. This config is globally readonly accessible via Jumbo.config.

Default Config File

config.js

const $cfg = require("jumbo-core/config-options").Configurations;
const $umjs = require("unimapperjs");
const MySqlAdapter = require("unimapperjs/adapters/MySqlAdapter");

/**
 * @name ApplicationConfig
 */
const applicationConfig = {
	/**
	 * Used for styles of Error reporting
	 * In development mode Errors will be shown in browser (browser errors not implemented yet) and in console
	 * In production mode Errors will be logged just to file if log enabled
	 */
	deployment: $cfg.Deployment.Development,

	/**
	 * For debuging; disable clustering and run app in one debugable process
	 */
	// debugMode: false,

	/**
	 * Protocol setting
	 * If you set HTTPS protocol specify privateKey and certificate paths
	 */
	protocol: {
		/**
		 * @default Http
		 */
		protocol: $cfg.Protocols.Http,

		/**
		 * Private key path
		 */
		privateKey: "data/private.key",

		/**
		 * Certificate path
		 */
		certificate: "data/certificate.crt",

		/**
		 * Or just PFX archive certificate
		 */
		pfx: "",

		/**
		 * Certifice passphrase
		 */
		passphrase: null
	},

	/**
	 * Multi-core support
	 */
	clustering: {
		/**
		 * 0 for automatic clustering driven by number of CPU's cores
		 * @default 0
		 */
		numberOfWorkers: 0
	},

	/**
	 * Enable template cache and define memory limit
	 */
	cache: {
		/**
		 * @default true
		 */
		enabled: true,

		/**
		 * Size limit for templates saved in memory
		 * JumboJS store often used templates in memory
		 * @default 10 MB
		 */
		memoryCacheSizeLimit: 10e6
	},

	/**
	 * Session configuration
	 */
	session: {
		/**
		 * Name of cookie which stores users's session ID
		 */
		sessionsCookieName: "JUMBOSESID",

		/**
		 * Length of session's life in days. It'll be deleted from disk after that time
		 * @type {Number} Number of days
		 * @default 30
		 */
		sessionLifetime: 30,

		/**
		 * Limit size of data saved in memory
		 * Not implemented yet
		 */
		memorySizeLimit: 20e6,

		/**
		 * Disable sessions saving to disk - speed boost
		 * When true, memorySizeLimit is ignored
		 * @default false
		 */
		justInMemory: false,
	},

	/**
	 * Enable log and set log level
	 */
	log: {
		/**
		 * @default true
		 */
		enabled: true,

		/**
		 * @default Warning
		 */
		level: $cfg.LogLevels.Warning
	},

	/**
	 * Maximal allowed number of requests per second. You can limit server stress.
	 * If more than specified request count will come, new requests in rest of one second obtain 429 code.
	 * Static files are counted into this number of requests
	 * @default null
	 * @type { Number || null }
	 */
	maxRequestPerSecond: null,

	/**
	 * Enable prevention of (D)DOS attacks
	 * It internally enable requests monitoring which will count number of requests per IP
	 * If IP makes more request per second new requests from that IP will be refused with code 403.
	 * Requests will be still accepted by server but framework will refuse to continue and save resources which
	 * proccessing of that request can framework take.
	 */
	DOSPrevention: {
		/**
		 * @default false
		 */
		enabled: false,

		/**
		 * The limit of request per second from same IP
		 * @description Warn! If you use framework static server and your index page have
		 * more than 100 links (scripts, styles, images etc.) client will be blocked!
		 * @default 100
		 */
		maxRequestPerIP: 100,

		/**
		 * Duration of IP blocking [in seconds]
		 * @default 3600
		 */
		blockTime: 3600
	},

	/**
	 * Allows you to use more languages (defined in URL right after first slash; eg. domain.tld/en-us/page/article/1)
	 */
	globalization: {
		/**
		 * Allow using languages
		 */
		enabled: true
	},

	/**
	 * Maximal size of POST data
	 * @default 5 MB
	 */
	maxPostDataSize: 5e6,

	/**
	 * Object with domains
	 */
	domains: {
		"default": $umjs.createDomain(MySqlAdapter, {
			host: "localhost",
			user: "test",
			password: "test",
			database: "node-task-manager"
		})
	}

	// You can define your own settings here,.. it'll be available via global Jumbo.config
};

module.exports = applicationConfig;

You can remove all properties with default values, this config extends the framework one with default values.

Handlers

Handlers are proprties in framework's classes which allow you to change some behavior. It's next level of configuration.

* : Jumbo.Application.Application.setBlockIpListener(listener: blockIpListener)

Instance method

You can handle IP blocking when IP / request / sec limit reached. You can block this IP in firewall or you can do whatever you want.

callback blockIpListener(blockedIP)
Params
  • blockedIP : String

* : Jumbo.Application.setStaticFileResolver(handler: staticFileResolver)

instance method

You can handle requests for static files on your own.

handler staticFileResolver(fileName: string, callback: staticFileResolverCallback)
Params
  • fileName : String Full file path, resolved
  • callback : staticFileResolverCallback Handler callback
callback staticFileResolverCallback(error: Error, readStream: fs.ReadStream, mime: string, size: number, headers?: { [key: string]: any })
Params
  • error : Error Error
  • readStream : fs.ReadStream File read stream of given static file
  • mime : String Expiration in seconds
  • size : Number size of given static file
  • optional headers : { [key: string]: any } Optional response headers

* : Jumbo.Application.Application.setTemplateAdapter(adapter: ITemplateAdapter)

Instance method

Set template adapter for view rendering.

Jumbo.Loger.Log.logFunction : logFunctionHandler

Static field

You can change the default logging and store log messages on your own.

callback logFunctionHandler(message, type)
Params
  • message : String
  • type : String

Default application script

app.js

/**
 * Application initial script
 */

// Get application from jumbo-core
let application = require("jumbo-core").application;

// Call route config for registering locations
require("./bootstrap/locator-config")(application.getLocator());

// Call DI registrar
require("./bootstrap/di-registrar")(application.getDIContainer());

// Register template adater - not needed if you want to use default adapter
// application.setTemplateAdapter(Jumbo.Adapters.TemplateAdapter);

// Register application for run at port 80; It'll run after framework do all async jobs
application.runWhenReady(80, function() {
	// You can do something after start
});

Routing

JumboJS has advanced routing system (called Locator) which allows you to define powerful dynamic routes (locations). In base project there is file /bootstrap/locator-config.js where locations are registered.

See API: Application: Locator for more information.

Locator config

/bootstrap/locator-config.js

/**
 * Locator configuration - setting host, sub-domains and locations
 * @param {Locator} locator
 */
module.exports = function(locator) {
	const types = locator.constructor.ParamType;

	/*
	 * DOMAINS
	 **************************************************************************************************/
	// Default sub-domain which will route to base app;
	// both urls with and without www will work
	locator.setMainSubdomain("www");

	// Create admin.yourdomain.tld which will route to /app/sub-apps/admin folder
	locator.addSubdomain("admin");


	/*
	 * LOCATIONS
	 **************************************************************************************************/
	// Allows you to change url delimiter from "/" to whatever you want
	// In this case "-" gonna be used, eg. /controllerexample-actionname-foo?bar=5&baz=0
	// But you still define locations with "/"
	locator.setDelimiter("-");

	// example; rewrite action name; limit id with RegExp
	locator.addLocation("locationName", "$controller/delete/$id", {
		"action": "deleteEntity", // no $action in location, so "deleteEntity" gonna be used
		"$id": /[0-9]+/,
		params: {
			parentId: 1 // this param is gonna be added to request with given value; as some default data
		}
	});

	// example2; specific URL pointing to HomeController.actionIndex
	// with optional parameter foo limited with RegExp
	locator.addLocation("locationName2", "Specific-url-what-ever-you-want[/$foo]", {
		"$foo": /[0-9]+/,
		"controller": "Home", // route to this controller
		"action": "index" // and this action
	});

	// Default route - let it last
	locator.addDefaultLocation("[$controller[/$action[/$id]]]");


	/*
	 * URL ALIASES
	 **************************************************************************************************/
	locator.addUrlAlias("/public/favicon.ico", "/favicon.ico"); // URL '/public/favicon.ico' has alias '/favicon.ico'
};

Locations

Just few points you should know about locations (routes).

  • There are variables $controller, $action and $globlanguage which stands for any controller, any action and locale.
  • $globlanguage stands for locale in URL, if you not place it in location string it's gonna be placed at the begining by locator as "$globlanguage/" + location string.
  • define your own parameter (slash param) with name starting with $,
  • use / (slash) as separator of location's parts (eg. $controller/$action),
  • you can use RegExp to restrict your parameter's value - to option object add property with key equals to your param's name, including $, as value set your RegExp,
  • you can set param's default value - to option object add object property params and add properties (param names) with required default value
  • use square brackets to mark something optional.
If need group inside param regex, use not-matching group(?:something).

Controllers

Controllers are some kind of entry points to your application. Controller's methods, which should be accessible from web, are called actions and must begin with keyword "action", "get", "post", "put" or "delete". This words tell framework you want let clients access this method from browser. Keyword "action" means all requests with all methods. The other words then stands for HTTP methods.

Controllers must be placed in /app/controllers/. Name convention is PascalCase and name must ends with keyword "Controller". Controller should be exported ES6 class and all actions must be async.

Default controller name is HomeController and default action name is actionIndex. Default action or controller name means that those names aren't used in URL. So yourweb.tld/ points to HomeController::actionIndex(). If you use long format URL with default names you'll be redirected to short format URL. It prevents duplicate content from occurring.
If some method in framework requires controller or action name, it's its name but without keyword. Eg. ExampleController's name is Example; actionLogout's name is logout.

Actions

What actions are is written in paragraphs above. Name convention is lowerCamelCase and name must starts with keyword "action" or lowercased method name. Actions must be async (ES7).

Action Parameters

All parameters (query params and location's params under defined names) from requested URL are injected to actions as parameters under their names in order you define them in action. Eg. if you request for /articles/delete/5?foo=bar, two params exist. First parameter is 5 which is defined in default location as id and second parameter is bar as foo. These two params will be injected to your action actionDelete(id, foo) {} to match names you defined. In that action you wait for id and foo. If these parameters exist in request, action will be called with that parameters in right order, matched by parameters names.

Returning methods

Returning methods are methods implemented in class Jumbo.Base.Controller and these methods end requests and return data or errors. See API: Base: Controller.

Example Controller

Example of controller

/**
 * // TODO: Describe your controller
 * @class ExampleController
 * @memberOf App.Controllers
 */
class ExampleController extends App.Controllers.BaseController {
	/**
	 * Default view action
	 */
	async actionIndex() {
		return "Hello world!";
	}

	async getSomeObject() {
		return { // JSON response
			foo: "bar"
		};
	}

	async getView() {
		return this.view({ optional: "data object" });
	}
}

module.exports = ExampleController;

Sequence diagram

There is some sequence diagram which show you how are actions called in controllers.

When request from client comes to server, Application handle that, verify request target and do some other stuff. Then if everything is ok, Application let ControllerFactory create ExampleController instance for given request.

When instance of ExampleController is created, internal _initController() method is called and request, response, session and diScope parameters are set to controller. Request and response are not accessible in controller's constructor.

Now it's time for calling action. But first there is beforeActions() method which you can use to verify user or initialize something or whatever else. This method can hadle the request and return result if you want, then action is not gonna be called. If you not return enything, the corresponding action's gonna be called. This method must be async too.

Sessions

Session is object in controller instance (property session) which is stored in memory, that allows you to store instances and sure it's much faster. But it's saved on disk too, because server can crash for some reason and maybe you store big data and have low memory on your server so disk provides you big storage for your sessions.

Dependency Injection

In JumboJS there is implemented constructor injection which allows you to obtain instances of your services in constructors and other classes. For example you have UserAuthService service which do some user auth stuff. You want instance of this service in controller so you must create its instance but UserAuthService need eg. 3 next services or some other class's instances. Without DI you must create all on your own. With DI, you just register your services and that's all. You write service name as parameter of constructor and framework resolve its instance for you.

Example codes are just examples.

Example without DI

In this example, you must create instance of each class on your own. If you have 10 controllers, with same or similar dependencies, you must write more than two hundred lines of code.

ExampleController.js

class ExampleController {
	constructor() {
		this.userAuthService = new App.Services.UserAuthService(
			new UserRepository(
				new Jumbo.Orm.EntityManager(
					new Jumbo.Orm.DBContext(Jumbo.Config.Database.default)
				)
			),
			new UserAccountRepository(
				new Jumbo.Orm.EntityManager(
					new Jumbo.Orm.DBContext(Jumbo.Config.Database.default)
				)
			),
			new SomeThirdNeededService(
				new SomeClass()
			)
		);

		this.someOtherService = new SomeOtherService(
			new EnterpriseRepository(
				new Jumbo.Orm.EntityManager(
					new Jumbo.Orm.DBContext(Jumbo.Config.Database.default)
				)
			),
			new CompanyRepository(
				new Jumbo.Orm.EntityManager(
					new Jumbo.Orm.DBContext(Jumbo.Config.Database.default)
				)
			),
			new SomeThirdNeededService(
				new SomeClass()
			)
		);
	}

	async beforeActions() {
		var user = await this.userAuthService.findUser(this.session.userId);

		if (user == null) {
			this.returnError(null, 403);
			return;
		}

		// Do something with user
	}
}

Example with DI

In this example, you have very simple controller and every other controller will look like this. You just register your classes (as services) and dependencies are resolved with framework automatically and injected to constructor.

ExampleController.js

class ExampleController {
	constructor(UserAuthService, SomeOtherService) {
		this.userAuthService = UserAuthService;
		this.someOtherService = SomeOtherService;
	}

	async beforeActions() {
		var user = await this.userAuthService.findUser(this.session.userId);

		if (user == null) {
			return this.error("This string is logged message", 403);
		}

		// Do something with user
	}
}

di-registrar.js

/**
 * Dependency registrar - registration of servics
 * @param {Jumbo.Ioc.DIContainer} container
 */
module.exports = function(container) {
	const LifetimeScope = Jumbo.Ioc.DIContainer.LifetimeScope;

	container.register(() => Jumbo.Config.Database.default, "DBConn",
		LifetimeScope.SingleInstance);

	container.register(Jumbo.Orm.DBContext,
		"DBContext", LifetimeScope.SingleInstance);

	container.register(Jumbo.Orm.EntityManager,
		"EntityManager", LifetimeScope.SingleInstance);

	container.register(App.Services.EnterpriseRepository,
		"EnterpriseRepository", LifetimeScope.ScopeInstance);

	container.register(App.Services.CompanyRepository,
		"CompanyRepository", LifetimeScope.ScopeInstance);

	container.register(App.Services.UserAccountRepository,
		"UserAccountRepository", LifetimeScope.ScopeInstance);

	container.register(App.Services.UserAuthService,
		"UserAuthService", LifetimeScope.ScopeInstance);

	container.register(App.Services.SomeThirdNeededService,
		"SomeThirdNeededService", LifetimeScope.ScopeInstance);

	container.register(App.Services.SomeOtherService,
		"SomeOtherService", LifetimeScope.ScopeInstance);
};
If you want to resolve class manually somewhere in code, you can use Jumbo.Ioc.DIContainer.instance.resolve("UserService") or this.scope.resolve("UserService") if you are in controller and you want to resolve your class in controller's Scope. See API: Ioc.

CLI

JumboJS has special package jumbo-developer which was created to support developing. It's console app which helps you with creating projects, controllers and their actions, services and more.

You can download jumbo-developer via npm npm install jumbo-developer -g. It'll be installed as system command so then just type in console jumbo-developer. With cmd help you will see list of available commands

API

Jumbo.Adapters

JumboJS is quite modular framework, it integrates many features but almost everything can be changed thanks adapters. Adapters are some classes which makes interfaces over some features.

For example template adapter. It's class which must implements 3 methods (render, preCompile and renderPreCompiled). You should use whatever template engine you want but you must create adapter for that engine, it means that you must implement those 3 methods which returns what framework wants. Then you just register your adapter in configuration and it's done.

Template Adapter

Template adapter demo

/**
 * Integrated template adapter for Jumplate
 */
var TemplateAdapter = {

	render: async function render(templatePath, layoutPath, dynamicLayout, data, context) {
		return "Basic function. Compile and render view and return complete result.";
	},

	preCompile: async function preCompile(templatePath, layoutPath, dynamicLayout) {
		return "If template engine contains precompilation which can be cached on disk and reused later, return precompiled code.";
	},

	renderPreCompiled: async function renderPreCompiled(compiledTemplate, data, context) {
		return "Process precompiled template and return final render.";
	},

	/**
	 * Extension of template files
	 */
	extension: ".jshtml",

	/**
	 * Tells that you implement preCompile and renderPreCompiled methods
	 */
	preCompilation: true
};

module.exports = TemplateAdapter;
templatePath is path to requested template. layoutPath is path to layout, can be null. data is object you call view from controller with. context is instance of controller for given request, you should want some extra data from controller (eg. current user's language).

Jumbo.Applicationnamespace

Namespace with core classes.

Locatorclass

Locator is some kind of Router which is used to define valid locations - URLs.

Class synopsis

setHost(host)

Set server hostname - used for subapp link creation in rare cases when host not known from request

Params
  • host : String

setDelimiter(delimiter)

Set URL part delimiter spliting parts of url (controller, action etc.) eg. delimiter "~" => domain.tld/controller~action~id

Params
  • delimiter : String String which will separate parts of URL

setMainSubdomain(subName)

Set default subdomain which will route to base app; both urls with and without main subdomain will work. It's good for cases when you host application on some subdomain eg. myapp.domain.tld. Main subdomain is set to "www" in default.

Params
  • subName : String Subdomain name, eg. "www"

addSubdomain(subName)

Register subapp to Locator as subdomain.

Params
  • subName : String Name of subapp

addLocation(locationName, location[, options = null[, subApp = null]])

Add new location to Locator.

Params
  • locationName : String Name of location
  • location : String Location string
  • optional options : Object Location options which should define specific controller or action or can limit parameters
  • optional subApp : String Name of subapp if you want to use this location only in given subapp

addDefaultLocation(location)

Set default location of Locator.

Params
  • location : String Location string

addUrlAlias(url, alias)

Create alias for some url. Eg. you have static file /public/robots.txt and you want this file under url /robots.txt so then add url alias addUrlAlias("/public/robots.txt", "/robots.txt").

Params
  • url : String Target URL
  • alias : String Alias for target URL

Requestclass

Request is wrap over Node.js's native request.

Class synopsis

class Request {
    request: http.IncomingMessage;
    subApp: string;
    location: ILocation;
    controller: string; // Name of subapp
    controllerFullName: string;
    action: string; // Name of action
    actionFullName: string;
    params: { // URL params
        [key: string]: any;
    };
    body: {{fields: {}, files: {}}};
    noCache: boolean;
    sessionId: string;
    locale: string; // Requested locale
    beginTime: number; // Request begin time
    method: string; // Requested HTTP method

    isXhr();
    getCookies();
    getCookie(name);
	getIP();
}

isXhr()

Returns

Boolean returns true if X-Requested-With == "XMLHttpRequest"

getCookies()

Returns

Object with cookies

getCookie(name)

Params
  • name : String Cookie name
Returns

String | null cookie's value

getIP()

Returns

String Client's IP

Responseclass

Response is wrap over Node.js's native response.

Class synopsis

setCookie(name, value[, expire[, domain[, path]]])

Params
  • name : String Cookie name
  • value : String Cookie value
  • optional expire : Number Expiration in seconds
  • optional domain : String Domain
  • optional path : String Path

unsetCookie(name)

Params
  • name : String Cookie name

redirectURL(url)

Redirect client to given URL.

Params
  • url : string

Jumbo.Basenamespace

Controllerclass

Controller is base class which you should extend for creating controllers. Controller implements basic methods which you will need for processing actions.

Class synopsis

class Controller {
	static clientMessagesId: string
	request: Jumbo.Application.Request
	response: Jumbo.Application.Response
	scope: Jumbo.Ioc.Scope
	session: { [key: string]: any; }
	crossRequestData: { [key: string]: any; }
	url: Jumbo.Utils.Url

	addMessage(message[, messageType]);
	exit();
	view([viewOrData[, data]]);
	renderView([viewOrData[, data]]);
	partialView([partialView[, data]]);
	template([view]);
	data(data[, type]);
	json(jsonObj);
	error(message[, statusCode]);
	fileDownload(filePath[, newName[, contentType]]);
	redirect(url);
}

exit()

Completely ends basic workflow. Call it if you processed request/response on your own

view([viewOrData[, data]])

Ends request and return default or given view with given data. This action automatically handle double-sided rendering header and return required type (view, template, data, partial)

Params
  • optional viewOrData : String | Object Name of specific view or just data if view is default (if match with action name)
  • optional data : Object Object with data for view

renderView([viewOrData[, data]])

Ends request and return rendered default or given view with given data.

Params
  • optional viewOrData : String | Object Name of specific view or just data if view is default (if match with action name)
  • optional data : Object Object with data for view

partialView([partialView[, data]])

Ends request and return partial view without layout.

Params
  • optional partialView : String | Object Name of spefic view or just data if view is default (if match with action name)
  • optional data : Object Object with data for view

template([view])

Return raw template corresponding to view.

Params
  • optional view : String Name of spefic view

addMessage(message[, messageType])

Add message to data for view, allow rendering messaes for clients. Messages are hold in cookie for next request or to time of first reading.

Params
  • message : String Your message for client
  • optional messageType : String Your custom type which you'll handle while rendering on your own

data(data[, type])

Ends request with result of given data and type

Params
  • data : Object Data to be send
  • optional type : String Mime type for gven data

json(jsonObj)

Accepts data in Object convert it to JSON and ends request with given data and application/json mime type

error(message[, statusCode = 500[, error]])

Ends request with error result. HTML file in /data/errors with given statusCode will be sent to client. If file not exists, plain text message "We're sorry but some error occurs." will be shown. Error object's details will be showed in browser in development mode.

Params
  • message : String Message which will be logged, it doesn't display to client
  • optional statusCode : Number default 500
  • optional error : Error | Exception Error object which details will be showed in browser in development mode

fileDownload(filePath[, newName[, contentType]])

Sends file to client for download

Params
  • filePath : String
  • optional newName : String
  • optional contentType : String

redirect(url)

Send response with location redirect.

Params
  • url : Url Instance of Jumbo.Utils.Url which is in controller under this.url getter

Jumbo.Iocnamespace

DIContainerclass

DIContainer is IoC container which register and resolve your classes.

Class synopsis

register(expr, as)

Register Type to container under given name as service. If you place that name to constructor, it'll be matched with this type and injected to that parameter.

Params
  • expr : Function Class or arrow function returning class or some object
  • as : String Registration name of service

resolve(name)

Resolve dependency which is registered under given name.

Params
  • name : String Registration name of Class
Returns

Return instance of registered type

resolveUnregistered(type)

Create instance of given type and resolve it's dependencies.

Params
  • type : Function Type which you want to resolve
Returns

Return instance of given type with resolved dependencies

Scopeclass

Scope is IoC scope which resolve your types and returns same instances for already resolved types in this scope.

Class synopsis

resolve(name)

Resolve dependency which is registered under given name.

Params
  • name : String Registration name of Class
Returns

Return instance of registered type

resolveUnregistered(type)

Create instance of given type and resolve it's dependencies.

Params
  • type : Function Type which you want to resolve
Returns

Return instance of given type with resolved dependencies

Jumbo.Loggingnamespace

JumboJS contain fully configurable logging with log levels. Messages are logged to files, each LogType has own file.

Logclass

Log is static class which give you ability log your messages. It accessible from global variable via Jumbo.Logging.Log.

Class synopsis

get LogTypes()

Returns

{ Http, Std } enum of available LogTypes

get LogLevels()

Returns

{ Error, Warning, Normal, Talkative } enum of available LogLevels

error(message[, type[, level]])

Params
  • message : String Message to log
  • optional type : Jumbo.Logging.Log.LogTypes Log type
  • optional level : Jumbo.Logging.Log.LogLevels Log level

warning(message[, type[, level]])

Params
  • message: String Message to log
  • optional type Log type
  • optional level Log level

line(message[, type[, level]])

Params
  • message Message to log
  • optional type Log type
  • optional level Log level

Jumbo.Utilsnamespace

Urlclass

Url is class which helps you create URLs. It copy locale, protocol, host and controller from given request.

Class synopsis

constructor(request)

Params
  • request: Jumbo.Application.Request

action(action[, controller[, params]])

Params
  • action: string
  • optional controller: string
  • optional params: Object
Returns

Url instance of itself

Packages

Jumplate

Jumplate is JumboJS's default template engine.

It's one of the fastest (faster than Jade, EJS, Handlebars.js, Underscore) Node.js template engine with cacheable precompilation. Jumplate is able to compile 16 000 templates in one second and render 400 000 precompiled templates in one second in one 2,33 Ghz core.

Template code is compiled to native JavaScript (can be cached), then it can be rendered with given set of data into final HTML.

Variables

All variables begin with $ symbol. Each variable can be printed as {{$variable}}. Output has escaped html entities (<, >, &, ", '). If you want to print variable without escaping use {{!$variable}}. It's possible to define variable right inside template with {{var $x = new Date().toString()}}

Comments

Jumplate has block coment which looks like this {{* Command brackets with asterisks *}}

Block

Define block of code which is rendered in place of definition. Can be reused with {{include blockName}}. Block can be included many times. {{block blockName}}Block content{{/block}}

Define

Define is similar to block, but define will not be rendered in place of definition. {{define defineBlockName}}Define block content will be rendered here{{/define}}

Defined

Existence of block and definition can be verified with {{defined defineBlockName}}{{/defined}}.

Include

Include block, define or other template file. {{include blockName}} will include block with given name. {{include "./path/to/template.tpl"}} will include file with given path. Double quotes required.

Cycles

There are two cycles. {{for $x = 0; $x < $count; $x++}} For content {{/for}} and {{for $item of $list}} Foreach content {{/for}}

In case of for..of there is special variable `$itemKey` which is accessible inside cycle block and contains key of current item.

In both cycles you can use commands {{first}}Will be rendered if this iteration is first{{/first}} {{last}}Will be rendered if this iteration is last{{/last}} {{even}}Will be rendered if this iteration is even{{/even}} {{odd}}Will be rendered if this iteration is odd{{/odd}}

Conditions

{{if $x == "condition"}}
	if condition true...
{{elseif $x == "else if conditio"}}
	if else if condition true...
{{else}}
	else...
{{/if}}

Localization

You can register localization hadler to Jumplate and then use command {{loc key.for.requested.translation}} which will call your handler with given key as parameter.

Jumplate.registerLocalizator(function (key, locale) {
	return SOME_EXAMPLE_MAP[locale][key];
});

Helpers

You can define own helpers, inline and block too. On class Jumplate there are static methods Jumplate.registerHelper(name, helper: (...argumets) => string) and Jumplate.registerBlockHelper(name, helper: (blockContent: string, ...arguments) => string)

Jumplate.registerHelper("link", function(text, url) {
	return `<a href="${url}">${text}</a>`;
});

Then you can use it in template like this.

{{block menu}}
	{{for $item of $menuItems}}
		{{link($item.text, $item.url)}}
	{{/for}}
{{/block}}

Predefined helpers

In default Jumplate template adapter there are some predefined helpers which should help you with some stuffs. There will be more helpers soon.

Form

Helper form is block helper which create <form> and </form> around your code. The begining tag will look like <form method="POST" action="#" enctype="multipart/form-data">

{{block content}}
	<h1>Registration</h1>
	{{form}}
		<input type="text" name="email">
		<input type="password" name="pass">
		<input type="submit" name="send" value="Sign Up">
	{{/form}}
{/block}

Link

Helper link is inline helper which create url. link(action[, controller[, params[, locale]]]

{{block menu}}
	{{for $item of $menuItems}}
		{{* Will create eg. /page/nodejs -> PageController.actionNodejs() *}}
		<a href='{{link($item.section, "Page")}}'>{{$item.text}}</a>
	{{/for}}
{{/block}}

Location Link

Helper locationLink is inline helper which create location url. locationLink(location, action[, controller[, params[, locale]]]

{{block menu}}
	{{for $item of $menuItems}}
		{{* Will create eg. /page/nodejs -> PageController.actionNodejs() by location registered as "default" *}}
		<a href='{{link("default", $item.section, "Page")}}'>{{$item.text}}</a>
	{{/for}}
{{/block}}

Applink

Helper applink is inline helper which create subapp url in same way as helper link. But here is new parameter at the begining which take sub-app name. locationLink(subapp, action[, controller[, params[, locale]]]

{{block adminMenu}}
	{{* Will create eg. admin.domain.tld/page/new -> PageController.actionNew() in admin subapp *}}
	<a href='{{applink("admin", "new", "Page")}}'>Create new page</a>
	<a href='{{applink("admin", "list", "User")}}'>List all users</a>
{{/block}}

uJumbo

uJumbo is client part of JumboJS. This micro-framework allows you to automate client side SPA creation. You just create uJumbo.Controller, give it content selector and that's all, your web is now SPA. See uJumbo Repository

Example Controller

(function(scope) {
	if (scope.AppController) return;

	var AppController = scope.AppController = class extends uJumbo.Controller {
		constructor() {
			super("body"); // Context selector
		}

		onInit() {
			console.log("Called on app init", arguments);
		}

		onBeforeNavigate(headers) {
			console.log("Called before navigatin", arguments)
			// return false; -> as prevent default
		}

		onNavigate(error, response) {
			if (error) {
				console.error(error);
				alert(error.message);
			}

			console.log("Called after navigation", arguments)
		}

		onPopState(state) {
			console.log("Called on browser BACK", arguments)
		}

		onBeforeFormSubmit(data, headers) {
			console.log("Called before form submit", arguments)
			// return false; -> as prevent default
		}

		onFormSubmit(error, response) {
			if (error) {
				alert(error.message);
			}

			console.log("Called after form response received", arguments)
		}

		myAction(hello) {
			alert(hello);
		}
	};

	scope.appCtrl = new AppController();
})(window);

As you can see, there are some on[Something] methods in the controller. That's optional handlers. There is method myAction too. It's yours defined method, which can be called from controller itself or from view by data-j-on events.

View Example

<form>
	<button data-j-onClick="myAction('Hello World')">Say Hello.</button>
</form>

UniMapperJS

UniMapperJS is JumboJS's default ORM. This ORM is based on Entity Framework from .NET. For queries you use JS functions and expressions same as you do with Array. See UniMapperJS.

Example query

// Students their name starts with 'P' or ends with 's'
let startsWith = "P";
let students = await Student.getAll()
	.filter(s => s.name.startsWith($) || s.name.endsWith("s"), startsWith)
	.sort(s => s.name)
	.slice(3, 8) // limit 5, skip 3
	.map(s => ({
		id: s.id,
		name: s.name
	}))
	.exec();