Creating your own web-page based JQuery + UB Server

This example demonstrates how to create a client part on the JQuery + Bootstrap platform and ensure its connection with the UB server. Create an authorization form, output information for unauthorized users, and display a list of applications that are available to the current user. The user will be able to edit the response to the application. Page view for unregistered users

nonAuthpage

Authorization

lofinForm

Requests list for an authorized user

authPage

Creating the single index.html page with scripts

Create a www subdirectory in the project folder and index.html in it. To connecting a web page to a UB server, you need to add the "inetPub" property to the "httpServer" section in ubConfig.json with a link to the directory where the html-code is stored.

{
 "httpServer": { // Built-in HTTP server configuration
   "host": "+", // Host name for server. +' will bound to all domain names for the specified port
   "port": "8881", //port to listen on
   "inetPub": "./www"
 }

In the index.html file, add a link to the lodash module.
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.11/lodash.min.js"></script>

And ub module <script src="/clientRequire/@unitybase/ub-pub/dist/ub-pub.min.js"></script>

To work with bootstrap and jquery-ui styles, connect the necessary styles and scripts

  <link rel="stylesheet" href="https://stackpath.bootstrapcdn.com/bootstrap/4.1.3/css/bootstrap.min.css"
        integrity="sha384-MCw98/SFnGE8fJT3GXwEOngsV7Zt27NXFoaoApmYm81iuXoPkFOJwJ8ERdknLPMO" crossorigin="anonymous">
  <link rel="stylesheet"
        href="http://getbootstrap.com/docs/4.1/examples/sticky-footer-navbar/sticky-footer-navbar.css">
  <link rel="stylesheet" href="https://use.fontawesome.com/releases/v5.3.1/css/all.css"
        integrity="sha384-mzrmE5qonljUremFsqc01SB46JvROS7bZs3IO2EmfFsd15uHvIt+Y8vEf7N7fWAU" crossorigin="anonymous">
  <link rel="stylesheet" href="https://code.jquery.com/ui/1.12.1/themes/base/jquery-ui.css">
  <script
          src="https://code.jquery.com/jquery-3.3.1.min.js"
          integrity="sha256-FgpCb/KJQlLNfOu91ta32o/NMZxltwRo8QtmkMRdAu8="
          crossorigin="anonymous"></script>
  <script
          src="https://code.jquery.com/ui/1.12.1/jquery-ui.min.js"
          integrity="sha256-VazP97ZCwtekAsvgPBSUwPFKdrwD3unUfSGVYrahUqU="
          crossorigin="anonymous"></script>

The styles and scripts for the JSGrid plug-in

  <link type="text/css" rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.css"/>
  <link type="text/css" rel="stylesheet"
        href="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid-theme.min.css"/>
  <script type="text/javascript" src="https://cdnjs.cloudflare.com/ajax/libs/jsgrid/1.5.3/jsgrid.min.js"></script>

Describe the static part of the page.

For documentation on styles and classes, see https://getbootstrap.com/.

<body>

<header>
    <!-- Fixed navbar -->
    <nav class="navbar navbar-expand-md navbar-dark fixed-top bg-dark">
        <a class="navbar-brand" href="https://unitybase.info/">
            <img alt="Brand" src="https://unitybase.info/img/logo.svg" height="30">
        </a>
        <button class="navbar-toggler" type="button" data-toggle="collapse" data-target="#navbarCollapse"
                aria-controls="navbarCollapse" aria-expanded="false" aria-label="Toggle navigation">
            <span class="navbar-toggler-icon"></span>
        </button>
        <div class="collapse navbar-collapse" id="navbarCollapse">
            <ul class="navbar-nav mr-auto">
                <li class="nav-item active">
                    <a class="nav-link" href="#">Home <span class="sr-only">(current)</span></a>
                </li>
                <li class="nav-item">
                    <a class="nav-link"
                       href="https://git-pub.intecracy.com/unitybase/samples/tree/master/courses/tutorial-v5">Tutorials</a>
                </li>
            </ul>
            <button type="button" class="btn btn-default navbar-btn navbar-right" style="display: none;"
                    id="btn-logout-nav">Logout
            </button>
            <button type="button" class="btn btn-default navbar-btn navbar-right" id="btn-login-nav">LogIn</button>
        </div>
    </nav>
</header>

<!-- Begin page content -->
<main role="main" class="container theme-showcase">
    <div class="jumbotron" id="welcome-container">
        <h1>Welcome to CITY PORTAL!</h1>
        <p>To leave a request, please call <i class="fa fa-phone-square" aria-hidden="true"></i> 2112</p>
        <p>To get started with the portal, please
            <button type="button" class="btn btn-primary" id="btn-login-j">LogIn</button>
        </p>
    </div>
    <div id='requests-container' style="display: none;">
        <h1>MY REQUESTS</h1>
        <div id="jsGrid" style="margin-bottom: 20px"></div>
        <button type="button" class="btn btn-primary" id="btn-get-req">Refresh my requests</button>
    </div>
</main>

<div id='popUp' title="Enter the login and password"></div>
<div id='errDialog' title="Error"></div>

<footer class="footer">
    <div class="container">
        <span class="text-muted">© 2017 City Portal </span>
    </div>
</footer>

<script src="src/app.js"></script>

</body>

Create a section <script></ script> and describe the function in it
$(function() {}) (analogue document.ready(function(){}))

Inside create page scripts

Add handlers to login buttons

ubAuth()

$('#btn-login-nav, #btn-login-j').click(function () {
    ubAuth()
})

$('#btn-get-req').click(function () {
    getMyRequests()
})

$('#btn-logout-nav').click(function () {
    if (window.$conn) window.$conn.logout().then(function () {
        getNonAuthContainers()
    })
})

function getAuthContainers() {
    $("#btn-login-nav").css({"display": "none"});
    $("#btn-logout-nav").css({"display": "inline"});
    $("#welcome-container").css({"display": "none"});
    $("#requests-container").css({"display": "inline"});
}

function getNonAuthContainers() {
    $("#btn-login-nav").css({"display": "inline"});
    $("#btn-logout-nav").css({"display": "none"});
    $("#welcome-container").addClass("jumbotron").css({"display": "block"});
    $("#requests-container").css({"display": "none"});
}

The function of connecting to a UB server

Function UB.Connect() return Promise<UBConnection>
Link to the documentation https://unitybase.info/api/ubpub-v5/module-@unitybase_ub-pub.html

function ubAuth() {
    UB.connect({
        host: window.location.origin,
        allowSessionPersistent: true,
        onCredentialRequired: function (conn, isRepeat) {
            return isRepeat
                ? Promise.reject(new UB.UBAbortError('invalid password or username'))
                : customConfirm(); //if session non-autorized call customConfirm() function
        },
        onAuthorizationFail: function (reason) {
            $("#errDialog")
                .html('<b>' + reason + '</b>')
                .dialog({
                    resizable: true,
                    modal: true,
                    buttons: {
                        "Retry": function () {
                            $(this).dialog("close");
                            customConfirm();
                        },
                        "Cancel": function () {
                            $(this).dialog("close");
                        }
                    }
                })
        }
    }).then(function (conn) {
        window.$conn = conn
        //Disable popup-window after F5 if session is authorized
        if ($('#popUp').hasClass('ui-dialog-content')) {
            $('#popUp').dialog("close");
        }
        getAuthContainers() //show content for autorized users
        getMyRequests()   //get requests list
    })
}

The function of creating an authorization pop-up window and verifying authentication

function customConfirm () {
  return new Promise(function (resolve, reject) {
    $("#popUp")
      .html('<input type="text" class="form-control" placeholder="Username" id="login"  style = "margin: 10px">' +
        '<input type="password" class="form-control" placeholder="Password" id="pwd" style = "margin: 10px">'
      )
      .dialog({
        resizable: true,
        modal: true,
        buttons: {
          "Login": function () {
            $(this).dialog("close");
            resolve({
              authSchema: 'UB',
              login: document.getElementById('login').value,
              password: document.getElementById('pwd').value
            })
          },
          "Cancel": function () {
            $(this).dialog("close");
            reject();
          }
        },
        focus: function () {
          //submit login form with Enter-key in password field
          $('#pwd').keydown(function (eventObject) {
            if (event.keyCode == 13) {
              $('#popUp').dialog("close");
              resolve({
                authSchema: 'UB',
                login: document.getElementById('login').value,
                password: document.getElementById('pwd').value
              })
            }
          });
        },
      })
  })
}

Description of JSGrid fields, its fields, data loading from the server, saving of edited rows.

Full documentation on the plugin http://js-grid.com/docs/

//Create custom template MyDateField for Date formatting in jsGrid
var MyDateField = function (config) {
    jsGrid.Field.call(this, config);
};
MyDateField.prototype = new jsGrid.Field({
    itemTemplate: function (value) {
        return new Date(value).toDateString();
    }
})
jsGrid.fields.myDateField = MyDateField;

function objectDiff(original, modified) {
    let diff = {};
    let modifiedKeys = Object.keys(modified)

    for (let i = 0, l = modifiedKeys.length; i < l; i++) {
        let key = modifiedKeys[i];
        if (original[key] !== modified[key])
            diff[key] = modified[key];
    }
    return diff;
}

const REQ_ENTITY = 'req_reqList'
const REQ_ATTRS = ['ID', 'reqDate', 'status', 'applicantInfo', 'reqText', 'answer', 'mi_modifyDate']
function getMyRequests () {
    window.$conn.Repository(REQ_ENTITY).attrs(REQ_ATTRS).select()
        .then(function (data) {
            $("#jsGrid").jsGrid({
                width: "100%",
                paging: true,
                autoload: true,
                editing: true,
                controller: {
                    loadData: function () {
                        return data
                    },
                    updateItem: function(newData) {
                        var d = $.Deferred(); //see documentation https://api.jquery.com/deferred.promise/
                        // To prevent unnecessary data modification we should pass only modified attributes
                        let original = _.find(data, {ID: newData.ID})
                        let diff = objectDiff(original, newData)
                        // add a ID to diff
                        diff.ID = newData.ID
                        // Since we know our entity support optimistic locks - pass a source modification date
                        diff.mi_modifyDate = original.mi_modifyDate
                        // see UBConnection.update documentation at https://unitybase.info/api/ubcore/UBConnection.html#update
                        //return
                        window.$conn.update({
                            entity: REQ_ENTITY,
                            fieldList: REQ_ATTRS,
                            lockType: 'Temp', // pessimistic lock
                            execParams: diff
                        }).then((response) => {
                            //return response
                            let responseAsObject = UB.LocalDataStore.selectResultToArrayOfObjects(response)[0]
                            // TODO - !@#@ js-grid not exit from edit mode
                            // may be it wait for a jQuery promise ?
                            //return $.Deferred().resolve(responseAsObject)
                            d.resolve(responseAsObject)
                        })
                        return d.promise();
                    }
                },
                fields: [
                    {name: "reqDate", type: "myDateField", width: 100, editing: false, title: 'Request date'},
                    {name: "status", type: "text", width: 50, editing: false, title: 'Status'},
                    {name: "applicantInfo", type: "text", width: 50, editing: false, title: 'Applicant info'},
                    {name: "reqText", type: "text", width: 150, editing: false, title: 'Text of request'},
                    {name: "answer", type: "text", width: 150, title: 'Answer'},
                    {
                        type: "control",
                        itemTemplate: function (value, item) {
                            var $result = $([]);
                            return $result.add(this._createEditButton(item));
                        }
                    }
                ]
            });
        })
}

The full code of the page with scripts is located at index-dev-common.html

Build a project using webpack

Webpack is a module bundler. Webpack takes modules with dependencies and generates static files that represent these modules
Installing the webpack: npm install -g webpack webpack-cli

Because this tools is installed globally, you need to set the system variable NODE_PATH

node_path_creating

To configure the webpack, create a configuration file in the project root named webpack.config.js with the following code

const webpack = require('webpack');
const path = require('path');

module.exports = {
    entry: './www/src/app.js',
    output: {
        path: path.resolve(__dirname, 'www'),
        filename: 'app.min.js'
    },
    module: {
        rules: [
            {test: /\.js$/, exclude: /node_modules/, loader: "babel-loader"}
        ]
    }
}

In this project, the webpack uses the babel-loader and DefinePlugin, which is used to define constants and expressions.
You need to install the babel-loader and preset babel plugins

npm install --save-dev @babel/core  
npm install --save-dev @babel/preset-env
npm install --save-dev babel-loader

After the installation in package.json will be added the following code

"devDependencies": {
    "@babel/core": "^7.1.0",
    "@babel/preset-env": "^7.1.0",
    "babel-loader": "^8.0.2"
  }

In www folder create the src\app.js file.
Cut the code inside the <body> <script> </script> </body> from the index.html file and paste it into the app.js file.
Also add line that includes the ub-pub module in the assembly to the minifed version of the script.

const UB = require('@unitybase/ub-pub')

The full code of app.js is below

const UB = require('@unitybase/ub-pub')

$(function () {
    ubAuth()

    $('#btn-login-nav').click(function () {
        ubAuth()
    })

    $('#btn-login-j').click(function () {
        ubAuth()
    })

    $('#btn-get-req').click(function () {
        getMyRequests()
    })

    $('#btn-logout-nav').click(function () {
        if (window.$conn) window.$conn.logout().then(function () {
            getNonAuthContainers()
        })
    })


//login with popUp dialog
    function customConfirm() {
        return new Promise(function (resolve, reject) {
            $("#popUp")
                .html('<input type="text" class="form-control" placeholder="Username" id="login"  style = "margin: 10px">' +
                    '<input type="password" class="form-control" placeholder="Password" id="pwd" style = "margin: 10px">'
                )
                .dialog({
                    resizable: true,
                    modal: true,
                    buttons: {
                        "Login": function () {
                            $(this).dialog("close");
                            resolve({
                                authSchema: 'UB',
                                login: document.getElementById('login').value,
                                password: document.getElementById('pwd').value
                            })
                        },
                        "Cancel": function () {
                            $(this).dialog("close");
                            reject();
                        }
                    },
                    focus: function () {
                        //submit login form with Enter-key in password field
                        $('#pwd').keydown(function (eventObject) {
                            if (event.keyCode == 13) {
                                $('#popUp').dialog("close");
                                resolve({
                                    authSchema: 'UB',
                                    login: document.getElementById('login').value,
                                    password: document.getElementById('pwd').value
                                })
                            }
                        });
                    },
                })
        })
    }

//Create custom template MyDateField for Date formatting in jsGrid
    var MyDateField = function (config) {
        jsGrid.Field.call(this, config);
    };
    MyDateField.prototype = new jsGrid.Field({
        itemTemplate: function (value) {
            return new Date(value).toDateString();
        }
    })
    jsGrid.fields.myDateField = MyDateField;

    function objectDiff(original, modified) {
        let diff = {};
        let modifiedKeys = Object.keys(modified)

        for (let i = 0, l = modifiedKeys.length; i < l; i++) {
            let key = modifiedKeys[i];
            if (original[key] !== modified[key])
                diff[key] = modified[key];
        }
        return diff;
    }

    const REQ_ENTITY = 'req_reqList'
    const REQ_ATTRS = ['ID', 'reqDate', 'status', 'applicantInfo', 'reqText', 'answer', 'mi_modifyDate']

    function getMyRequests() {
        window.$conn.Repository(REQ_ENTITY).attrs(REQ_ATTRS).select()
            .then(function (data) {
                $("#jsGrid").jsGrid({
                    width: "100%",
                    paging: true,
                    autoload: true,
                    editing: true,
                    controller: {
                        loadData: function () {
                            return data
                        },
                        updateItem: function (newData) {
                            var d = $.Deferred(); //see documentation https://api.jquery.com/deferred.promise/
                            // To prevent unnecessary data modification we should pass only modified attributes
                            let original = _.find(data, {ID: newData.ID})
                            let diff = objectDiff(original, newData)
                            // add a ID to diff
                            diff.ID = newData.ID
                            // Since we know our entity support optimistic locks - pass a source modification date
                            diff.mi_modifyDate = original.mi_modifyDate
                            // see UBConnection.update documentation at https://unitybase.info/api/ubcore/UBConnection.html#update
                            //return
                            window.$conn.update({
                                entity: REQ_ENTITY,
                                fieldList: REQ_ATTRS,
                                lockType: 'Temp', // pessimistic lock
                                execParams: diff
                            }).then((response) => {
                                //return response
                                let responseAsObject = UB.LocalDataStore.selectResultToArrayOfObjects(response)[0]
                                // TODO - !@#@ js-grid not exit from edit mode
                                // may be it wait for a jQuery promise ?
                                //return $.Deferred().resolve(responseAsObject)
                                d.resolve(responseAsObject)
                            })
                            return d.promise();
                        }
                    },
                    fields: [
                        {name: "reqDate", type: "myDateField", width: 100, editing: false, title: 'Request date'},
                        {name: "status", type: "text", width: 50, editing: false, title: 'Status'},
                        {name: "applicantInfo", type: "text", width: 50, editing: false, title: 'Applicant info'},
                        {name: "reqText", type: "text", width: 150, editing: false, title: 'Text of request'},
                        {name: "answer", type: "text", width: 150, title: 'Answer'},
                        {
                            type: "control",
                            itemTemplate: function (value, item) {
                                var $result = $([]);
                                return $result.add(this._createEditButton(item));
                            }
                        }
                    ]
                });
            })
    }

    function getAuthContainers() {
        $("#btn-login-nav").css({"display": "none"});
        $("#btn-logout-nav").css({"display": "inline"});
        $("#welcome-container").css({"display": "none"});
        $("#requests-container").css({"display": "inline"});
    }

    function getNonAuthContainers() {
        $("#btn-login-nav").css({"display": "inline"});
        $("#btn-logout-nav").css({"display": "none"});
        $("#welcome-container").addClass("jumbotron").css({"display": "block"});
        $("#requests-container").css({"display": "none"});
    }

    function ubAuth() {
        UB.connect({
            host: window.location.origin,
            allowSessionPersistent: true,
            onCredentialRequired: function (conn, isRepeat) {
                return isRepeat
                    ? Promise.reject(new UB.UBAbortError('invalid password or username'))
                    : customConfirm(); //if session non-autorized call customConfirm() function
            },
            onAuthorizationFail: function (reason) {
                $("#errDialog")
                    .html('<b>' + reason + '</b>')
                    .dialog({
                        resizable: true,
                        modal: true,
                        buttons: {
                            "Retry": function () {
                                $(this).dialog("close");
                                customConfirm();
                            },
                            "Cancel": function () {
                                $(this).dialog("close");
                            }
                        }
                    })
            }
        }).then(function (conn) {
            window.$conn = conn
            //Disable popup-window after F5 if session is authorized
            if ($('#popUp').hasClass('ui-dialog-content')) {
                $('#popUp').dialog("close");
            }
            getAuthContainers() //show content for autorized users
            getMyRequests()   //get requests list
        })
    }
});

In the console at the root of the project, run the webpack command.
The result in the www folder will create the file app.min.js
In the <header> </header> section, delete the links to the following modules, because they are included in app.min.js

<script src="/clientRequire/@unitybase/ub-pub/dist/ub-pub.min.js"></script>

Creating a dev- version of the project.

Create the file www\index-dev.html and copy the code from index.html into it.
Instead of linking to a minifed version of the script, add a link to app.js
<script src="src/app.js"></script>
and if the following link was deleted, then it must be returned

<script src="/clientRequire/@unitybase/ub-pub/dist/ub-pub.min.js"></script>

Now, at http://localhost:8881/index-dev.html, the project is available for debugging.

Next step