Introduction

The "Regoch Web" is framework for developing single page, dynamic web applications. Created in modern Javascript with recent version of ECMAScript it's extraordinary simple and intuitive.
Together with the Cordova it can be used for mobile app development.
Learn this framework in just a few hours.

 The website regoch.org is completely made in the Regoch WEB framework.

Features

  • no slow compilation as in Angular, Vue or React (no such compilation at all)
  • lightweight application - small app file size (~50kB only)
  • JS files builded with the gulp and browserify (very fast)
  • use CommonJS and write the code just like you are doing that in NodeJS by using require()
  • no typescript, no heavy compiling, no bullshit
  • Model-View-Controller (MVC), intuitive app structure
  • easy to learn and to use
  • due to its small size you can build very fast and reactive mobile apps
  • steep learning curve (you'll reach high programming skills rapidly fast)

Installation & Development

To start a new project clone the regoch-web-skel repository. It's the web app skeleton with the basic routes and controllers.


  $ git clone https://github.com/smikodanic/regoch-web-skel.git projectName
  $ cd projectName
  $ rm -rf .git
  $ git init
  $ npm run inst
  

Now edit regoch.json, package.json, README.md and files in the client and server directories.
Notice: Instead of using the git clone the skel can be downloaded from https://github.com/smikodanic/regoch-web-skel/archive/refs/heads/master.zip

Build & Server Deployment

Build and deploy your application on the server. The SSR (Server Side Render) is achieved with the ProxyServer.


  $ sudo npm install -g pm2
  $ git clone <projectName>
  $ cd projectName
  $ npm run build   (or $ gulp build)
  $ pm2 start server --name projectName
  

Quick Start

After cloning the regoch-web-skel edit client/src/app.js file and create new controllers.

const { App } = require('regoch-web');

// route definitions
const routesCnf = [
  ['when', '/', 'HomeCtrl'],
  ['when', '/contact', 'ContactCtrl'],
  ['when', '/product/:id', 'ProductCtrl'],
  ['notfound', 'NotfoundCtrl'],
];

// inject controllers and define routes
app
  .controllers([HomeCtrl, ContactCtrl, ProductCtrl, NotfoundCtrl])
  .routes(routesCnf);


Playground

To learn more quickly visit our playground examples. The controller code is here and HTML views are here.
  • Controller Lifecycle Hooks - shows in which order will be executed constructor, prerender, postrender, init, destroy
  • Model - modelling with the Proxy Object
  • View::rgInc() - test the rgInc() method with nested data-rg-inc elements
  • View::loadViews() - test the loadViews() method with isAsync argument
  • View::lazyJS() - load/unload JS file lazily
  • DataRg - test the data-rg- elements related to DataRg methods
  • DataRgListeners - test the data-rg- elements related to DataRgListeners methods
  • Cookie - test all Cookie methods: put, putObject, getAll, ...
  • Form - test all Form methods: setControl, getControl, delControl ...
  • Auth - test login, logout, ... i.e. Auth methods
  • navig - test Navig methods

License

The software is published under MIT License.
The MIT License

Copyright (C) 2021  Saša Mikodanić http://www.regoch.org

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
files (the "Software"), to deal in the Software without restriction, including without limitation the
rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to
whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

App

class App

Properties

PropertyDescriptionTypeDefault
ctrlsA collection of all controllers. This makes possible to use a controller's methods inside another controller.object{}
$debugOptsDebugger options.object

client/src/conf/$debugOpts.js
=================================
{
// general
warnings: false,

// Router
router: false,
regochRouter: false,

// Controller.js
render: false,
navig: false,

// View.js
rgInc: false,
loadView: false,
emptyView: false,
loadHead: false,
rgLazyjs: false,

// DataRg.js
rgFor: false,
rgRepeat: false,
rgPrint: false,

rgIf: false,
rgSpinner: false,
rgSwitch: false,
rgDisabled: false,
rgValue: false,
rgChecked: false,
rgClass: false,
rgStyle: false,
rgSrc: false,
rgAttr: false,
rgElem: false,
rgEcho: false,
rgFlicker: false,

// DataRgListeners.js
rgKILL: false,
rgHref: false,
rgClick: false,
rgKeyup: false,
rgChange: false,
rgEvt: false,
rgSet: false,
rgModel: false
};
      


Beside these properties the global window.regochGlob = {} variable is initialised and can be used in controllers whenever it's needed.

Methods

  • controllers (Ctrls :Class[]) :App - create controller instances and inject into the app.ctrls

    Ctrls - array of controller classes, for example: [HomeCtrl, LoginCtrl]

  • fridge (name, val) :App - Set the subproperty of the controller's $fridge property in all controllers. The $fridge object will be preserved during controller processing execution. Other controller's properties will be deleted. This method is useful to define values shared among the all controllers.

    name - $fridge property name val - the fridge property value

  • auth (auth :Auth) :App - Inject the auth library into the all controllers and use it as this.$auth. Useful in apps where authentication guards are required in all routes, for example when building a web panel.

    auth - instance of the Auth class

  • preflight (funcs :Function) :App - Define preflight functions which will be executed on every route, before the controller processing() i.e. before loader(). The function argument is trx, regoch router transitional variable. Never define $model in the preflight function because it will triger render() before loader().

    funcs - the comma separated functions, preflight(func1, async (trx) => {console.log(trx)})

  • postflight (funcs :Function) :App - Define postflight functions which will be executed on every route, after the controller processing(), i.e. after the postrend(). Here the $model can be defined (what will trigger the render()).

    funcs - the comma separated functions, postflight(func1, async (trx) => {console.log(trx)})

  • routes (routesCnf :any[][]): App - define the app routes

    routesCnf - route configuration, for example:
    [['when', '/login', 'LoginCtrl'], {autoLogin:true}], ['when', '/customer/product/:id', 'CustomerProductCtrl'], {isLogged:true, hasRole:true}]]

  • viewsCached (viewsCached :object) :App - Inject the content of the client/_cache/views.json. Useful to speed up the HTML view load, especially in data-rg-inc elements. The cached views are set in the window.regochGlob.viewsCached

    viewsCcached - the content of the client/_cache/views.json file. In the regoch.json is defined what files will be cached.

  • debugger ($debugOpts :object) :App - Define the debugging options. Set the controller's $debugOpts property.

    funcs - the comma separated functions, postflight(func1, async (trx) => {console.log(trx)})



  • EVENT LISTENERS

  • onReady (cb :Function): void - Fired when HTML doc with the all resources is loaded. https://developer.mozilla.org/en-US/docs/Web/API/GlobalEventHandlers/onload
  • onDOMLoaded (cb :Function): void - Fired when HTML doc is loaded without CSS, IMG and other resources. https://developer.mozilla.org/en-US/docs/Web/API/Window/DOMContentLoaded_event
  • createDOMObserver (cb :Function): void - Listen for the DOM changes. Creates app.DOMobserver. https://developer.mozilla.org/en-US/docs/Web/API/MutationObserver
    
    app.createDOMObserver((mutationsList, observer) => { ... });
    const targetNode = document.querySelector('p#my-id);
    const config = { attributes: true, childList: true, subtree: true };
    app.DOMObserver.observe(targetNode, config);
    
    To stop observing the DOM changes use: app.DOMObserver.disconnect();
          

Example


const { App, syslib } = require('regoch-web');
const viewsCached = require('../_cache/views.json');
const routes = require('./routes');

// conf
const { $debugOpts, authOpts } = require('./conf');

// controllers
const HomeCtrl = require('./controllers/HomeCtrl');
const WebsocketServerCtrl = require('./controllers/WebsocketServerCtrl');
const WebsocketClientsCtrl = require('./controllers/WebsocketClientsCtrl');
const WebsocketClientsNodejsCtrl = require('./controllers/WebsocketClientsNodejsCtrl');
const WebsocketClientsBrowserCtrl = require('./controllers/WebsocketClientsBrowserCtrl');
const WebCtrl = require('./controllers/WebCtrl');
const MobCtrl = require('./controllers/MobCtrl');
const DatabaseCtrl = require('./controllers/DatabaseCtrl');
const RouterCtrl = require('./controllers/RouterCtrl');
const ContactCtrl = require('./controllers/ContactCtrl');
const NotfoundCtrl = require('./controllers/NotfoundCtrl');

// controllers - playground
const Controller_hooksCtrl = require('./controllers/playground/Controller_hooksCtrl');
const ModelCtrl = require('./controllers/playground/ModelCtrl');
const View_rgIncCtrl = require('./controllers/playground/View_rgIncCtrl');
const View_loadViewsCtrl = require('./controllers/playground/View_loadViewsCtrl');
const View_lazyJSCtrl = require('./controllers/playground/View_lazyJSCtrl');
const DataRgCtrl = require('./controllers/playground/DataRgCtrl');
const DataRgListenersCtrl = require('./controllers/playground/DataRgListenersCtrl');
const CookieCtrl = require('./controllers/playground/CookieCtrl');
const FormCtrl = require('./controllers/playground/FormCtrl');
const LoginCtrl = require('./controllers/playground/LoginCtrl');
const LoginokCtrl = require('./controllers/playground/LoginokCtrl');
const Navig1Ctrl = require('./controllers/playground/Navig1Ctrl');
const Navig2Ctrl = require('./controllers/playground/Navig2Ctrl');

// auth
const auth = new syslib.Auth(authOpts);

// preflight/postflight
const pref1 = async (trx) => { console.log('PREFLIGHT 1 - trx::', trx); };
const pref2 = async (trx) => { console.log('PREFLIGHT 2 - trx::', trx); };
const postf1 = async (trx) => { console.log('POSTFLIGHT 1 - trx::', trx); };
const postf2 = async (trx) => { console.log('POSTFLIGHT 2 - trx::', trx); };

// app
const app = new App();
app
.controllers([
// docs
HomeCtrl,
WebsocketServerCtrl,
WebsocketClientsCtrl,
WebsocketClientsNodejsCtrl,
WebsocketClientsBrowserCtrl,
WebCtrl,
MobCtrl,
DatabaseCtrl,
RouterCtrl,
ContactCtrl,

// playground
Controller_hooksCtrl,
ModelCtrl,
View_rgIncCtrl,
View_loadViewsCtrl,
View_lazyJSCtrl,
DataRgCtrl,
DataRgListenersCtrl,
CookieCtrl,
FormCtrl,
LoginCtrl,
LoginokCtrl,
Navig1Ctrl,
Navig2Ctrl,

// not found
NotfoundCtrl
])
.auth(auth) // needed for route authGuards
.preflight(pref1, pref2)
.postflight(postf1, postf2)
.routes(routes)
.viewsCached(viewsCached)
.debugger($debugOpts);
    

Controller

class Controller extends Model

The controller defines what logic will be applied on the specific route. Every controller should be injected in the app with the App.controllers() method. The controller class is extended with the MVC classes (see sys/mvc/ folder).
MyController → Controller → Model → View → DataRg → DataRgListeners → Aux

Properties

PropertyDescriptionType
$authInstance of the lib/Auth. Created by App.auth()object
$debugOptsDebugging options. Created by App.debugger()object
$fridgeFridged objects which will not be changed during the render process. Created by App.fridge()object
$modelThe container for model variables.object
$modelerContains the model methods like: setValue, getValue, mpush, mpop, munshift, mshift.object
$preflightContains preflight functions. Created by App.preflight()object
$postflightContains postflight functions. Created by App.postflight()object
$rgRelated to DataRg and DataRgListeners parameters: {elems, listeners, separator, varnameChars}object
$httpClientDefault HTTP client set up for quering the HTML docs from the client/src/viewsobject
$baseURIhostThe server's URI base, for example http://localhost:4400string

Methods

The main purpose of the controller's processing methods is to render HTML elements with data-rg- attribute. The Regoch Web framework will render all elements with data-rg- attributes. Here is the example of one data-rg- element: <span data-rg-print="product.name"></span> which will set SPAN innerHTML with product.name (controller property value).

    LifeCycle Hook Methods

    In the controller processing the developer can use 5 lifecycle hooks: loader(), init(), rend(), postrend() and destroy(). The preflight if defined with app.preflight() will be executed on every route.
    PREFLIGHT → LOADER → INIT → REND → POSTREND → DESTROY → POSTFLIGHT

  • async loader (trx :object) :Promise<void>

    A hook to load the HTML and other resources. Use View methods here (loadView, ...).
    trx - regoch router transitional variable (carrier), see here

  • async init (trx :object) :Promise<void>

    A hook to initialise the controller properties with the initial values. This is the right place for the API calls.
    trx - regoch router transitional variable (carrier)

  • async rend (trx :object) :Promise<void>

    Render all elements with the "data-rg-..." attribute i.e. DataRg and DataRgListeners. If this method is omitted then default render() will be executed. Recommended not to use this method until you exactly know what you are doing.
    trx - regoch router transitional variable (carrier)

  • async postrend (trx :object) :Promise<void>

    It's a controller lifecycle hook which executes the controller code after the rend() method i.e. when the complete HTML is generated.
    trx - regoch router transitional variable (carrier)

  • async destroy (pevent :Event) :Promise<void>

    Executes the controller code on destroying the controller, i.e. on navig.onUrlChange event.
    This method will terminate all data-rg listeners activated by current controller (for example data-rg-click which listens for clicks on the element). This is very important to save browser's (app) memory space and to prevent memory leaks.
    pevent - the 'popstate' (back/forward buttons) or 'pushstate' (data-rg-href element click) event



  • Render Methods

    Very rarely the methods render() and renders() should be used because the render() will be executed automatically every time when this.$model.prop = ... is used in the controller.
  • async render (attrValQuery :string, renderDelay :number) :Promise<void>

    Render all data-rg- elements which matched by attrValQuery.
    attrValQuery - controller property (when used the number of render data-rg- elements is reduced → speed up render process)
    renderDelay - delay in miliseconds between each render phase

  • async renders (attrValQuerys :string[], renderDelay :number) :Promise<void>

    Use multiple render() method in one method. This is alias for render() used multiple times.
    attrValQuerys - array of the controller property names: ['company.name', 'company.year']
    renderDelay - delay in miliseconds between each render phase

Controller Example

This is the example with all LifeCycle methods.

const { Controller, syslib } = require('regoch-web');

module.exports = class HomeCtrl extends Controller {

  constructor(app) {
    super();
  }

  /*** LifeCycle Methods ***/
  async loader(trx) {
    this.setTitle('The Regoch Project');
    this.setDescription('The Regoch Project is bundle of developer tools and frameworks for realtime, web and mobile applications: regoch websocket server and client, single page app, database.');
    this.setKeywords('regoch, websocket, realtime, mobile applications, single page app, database');
    this.setLang('en');

    this.loadCSS([
      'https://cdnjs.cloudflare.com/ajax/libs/prism/1.23.0/themes/prism-coy.min.css'
    ]);

    await this.loadView('#primary', 'pages/home/primary.html', 'sibling');
    await this.loadViews([
      ['#top', 'pages/shared/top.html'],
      ['#main', 'pages/home/main.html'],
      ['#bottom', 'pages/home/bottom.html']
    ], true);
  }

  async init(trx) {
    this.$model.a = 23;
  }

  // if rend() is not defined then default render() will be executed
  async rend(trx) {
    this.rgfor();
  }

  async postrend(trx) {
    await this.lazyJS([
      'https://cdnjs.cloudflare.com/ajax/libs/highlight.js/9.15.8/highlight.min.js'
    ], 400);
  }

  async destroy(elem, event) {
    // CODE TO EXECUTE WHEN URL IS CHANGED i.e. WHEN CONTROLLER IS DESTROYED
    this.unlazyJS();
  }


  /*** Page Fuctionality Methods ***/
  myKlik() {
    console.log('This is INIT test.');
  }


};