JavaScript Style Guide

Data layer for accessing UnityBase server from Browser or NodeJS

NodeJS example

global.XMLHttpRequest = require('xhr2')
const UB = require('@unitybase/ub-pub')

const HOST = process.env.UB_HOST || 'http://localhost:8881'
const USER = process.env.UB_USER || 'admin'
const PWD = process.env.UB_PWD || 'admin'

async function call_ub () {
  const conn = await UB.connect({
    host: HOST,
    onCredentialRequired: function (conn, isRepeat) {
      if (isRepeat) {
        throw new UB.UBAbortError('invalid credential')
      } else {
        return Promise.resolve({authSchema: 'UB', login: USER, password: PWD})
      }
    },
    onAuthorizationFail: function (reason) {
      console.error(reason)
    }
  })

  console.log(`
Hello, ${conn.userLogin()}!
We know that you are ${JSON.stringify(conn.userData(), null, ' ')}
`)

  conn.get('stat').then(function (statResp) {
    console.log('Current server statistics:', statResp.data)
  })

  const items = await conn.Repository('ubm_navshortcut').attrs(['ID', 'code', 'caption'])
   .limit(2)
      .selectAsObject()
      .then(function (data) {
        console.log('First 2 adminUI shortcuts:')
        console.log(JSON.stringify(data, null, '\t'))
      })
  console.table(items)
}

try {
  call_ub()
} catch (e) {
  console.error(e)
}

The same code as above will work in browser (just comment first line where XMLHttpRequest is required).

Connecting to UB server

The main entry point is connect method.

  const UB = require('@unitybase/ub-pub')
  const conn = UB.connect({
    host: 'https://myserver.com',
    onCredentialRequired: function(conn, isRepeat){
       if (isRepeat){
           throw new UB.UBAbortError('invalid credential')
       } else {
           return Promise.resolve({authSchema: 'UB', login: 'myuser', password: 'mypassword'})
       }
    },
    onAuthorizationFail:  function(reason){
       alert(reason)
    }
  })

After connecting UBConnection class cares about reconnect, data cashing, request buffering and proper data serialization.

Session

Connection contains the information about currently logged-in user. The application logic on the server side can add any custom properties required for application when user is already logged-in. Such properties are available on the client in connection.userData()}

  console.log(`
    Hello, ${conn.userLogin()}!
    We know that you are ${JSON.stringify(conn.userData())}
  `)

Domain

connection.domain contains information about the application domain - the list of models, entities, entities' attributes and methods. Domain is already localized to the language of logged-in user.

This information should be used by the client application during building the UI. For example:

let usersEntity = conn.domain.get('uba_user')
// localized caption of entity uba_user
console.log(usersEntity.caption)
// localized
console.log(`Input control for user name
  should be of type ${usersEntity.attributes.name.dataType}
  and with label ${usersEntity.attributes.name.caption}
`)
console.log(`Control for selecting the user from the list
  should use ${usersEntity.getDescriptionAttribute()}
  as a list content attribute`)

console.log(`Currently logged-in user
  ${u.haveAccessToMethod('update') ? 'can' : 'can not'} edit uba_user`)

Querying data

In most cases client retrieves data from the server using UBQL (UnityBase Query Language) JSON.

connection.Repository fabric function is a helper for building UBQL JSON

conn.Repository('my_entity').attrs(['ID', 'code'])
 .attrs('attrOfEntityType.caption') // JOIN to other table
 .where('code', 'in', ['1', '2', '3'])  // code in ('1', '2', '3')
 .where('name', 'contains', 'Homer'). // name like '%homer%'
 //(birthday >= '2012-01-01') AND (birthday <= '2012-01-02')
 .where('birthday', 'geq', new Date()).where('birthday', 'leq', new Date() + 10)
 .where('[age] -10', '>=', {age: 15}, 'byAge') // (age + 10 >= 15)
 .where('', 'match', 'myvalue') // FTS query
 .selectAsObject().then(function(response){
    // here response is in [{ID: 10, code: 'value1'}, .... {}] format
 })

See Repository method documentation in ClientRepository

Buffering

Several UI controls can simultaneously send queries using one connection. In this case several queries that come in the same 20ms period of time will be buffered and sent to the server as a single HTTP request to reduce network bandwidth and latency.

This happens automatically - just write the code as usual and let the connection care about network performance. Run the code below in console and look into the Network - you will see the single HTTP request

Promise.all([
  conn.Repository('uba_user').attrs('ID').selectAsArray(),
  conn.Repository('uba_group').attrs('ID').selectAsObject()
]).then(UB.logDebug)

Caching

Server-side developer can decide that some of the entities are changed infrequently and contain small amount of data. In this case such entities are marked as cached. The repository is aware of such entities by using the information from Domain, and can return data without sending HTTP request over the wire. Internally the repository uses LocalDataStorage to filter and sort data locally.

Test it from the console:

// first call to cached entity will get data from server
UB.Repository('ubm_enum').attrs(['ID', 'code'])
 .where('code', 'startsWith', 'I').selectAsObject()
 .then(UB.logDebug)
// second - filter data locally, even if filter condition is changed
UB.Repository('ubm_enum').attrs(['ID', 'code'])
 .where('code', 'startsWith', 'UPD').selectAsObject()
 .then(UB.logDebug)

Promisified XHR

As a side effect @unitybase/ub-pub module contains "Promisified" API for HTTP request:

  • xhr: An asynchronous HTTP request. Returns a {Promise} object
  • get: simplified xhr for GET
  • post: simplified xhr for POST

So you do not need axios etc. Just use a UB.xhr, UB.get or UB.post:

  const UB = require('@unitybase/ub-pub')
  UB.get('https://unitybase.info').then(resp => console.log(resp.data))

Submodules

Members

# appConfig deprecated static

Use connection.appConfig instead

# ClientRepository : ClientRepository static

# connection : UBConnection static

After call to UB.connect this property will point to the active connection

# CryptoJS static

CryptoJS instance (included modules are enc-base64, sha256, md5)

# formatter : formatByPattern static

Locale based Date and Number formatters, See details in @unitybase/cs-shared/formatByPattern

  
      const d = new Date(2020, 04, 23, 13, 14)
 UB.formatter.formatDate(d, 'date') // without 3rd lang parameter - will be formatted for user default lang (for uk - 23.05.2020)
 UB.formatter.formatDate('2020-05-23', 'date', 'uk') // 23.05.2020
 UB.formatter.formatDate(d, 'date', 'en') // 05/23/2020
 UB.formatter.formatDate(d, 'dateTime', 'uk') // 23.05.2020 13:14
 UB.formatter.formatDate(d, 'date', 'en') // 05/23/2020, 1:14 PM
 const n = 2305.1
 UB.formatter.formatNumber(n, 'sum', 'en') // 2,305.10
 UB.formatter.formatNumber('2305.1', 'sum', 'en') // 2,305.10
 UB.formatter.formatNumber(n, 'sum') // without 3rd lang parameter - will be formatted for user default lang (for uk "2 305,10")
  

# i18n static

Return locale-specific resource from its identifier. localeString must be:

  • either previously defined dy call to i18nExtend
  • or be a combination of entity and attribute names so that UB.i18n('uba_user') or UB.i18n('uba_role.description') would be resolved to localized entity caption or entity attribute caption
  • description/documentation/captionSingular of entity/attribute can be localized using hashtag:
    • #description - an entity description
    • #documentation - an entity documentation
    • #captionSingular - if entity.captionSingular is defined - entity.captionSingular, else - entity.caption See samples below

  
      //Localized string can be formatted either by position args:
 UB.i18nExtend({
   greeting: 'Hello {0}, welcome to {1:i18n}',
   Kiev: 'Kyiv city'
 })
 UB.i18n('greeting', 'Mark', 'Kiev') // Hello Mark, welcome to Kyiv city
 // in sample above :i18n modifier is added to the second format args, so `Kiev` is also translated

 //Or by named args:
 UB.i18nExtend({
   namedGreeting: 'Hello {name}, welcome to {place}'
 })
 UB.i18n('namedGreeting', {name: 'Mark', place: 'Kiev'}) // Hello Mark, welcome to Kiev

 //Localization itself can be an object:
 UB.i18nExtend({
   loginPage: { welcome: 'Welcome to our app', user: 'Dear {user}'}
 })
 UB.i18n('loginPage.welcome') // Welcome to our app
 UB.i18n('loginPage.user', {user: 'Pol}) // Dear Pol
 UB.i18n('loginPage') // return object {welcome: "Welcome to our app", user: "Dear {user}"}

 UB.i18n('uba_user') // -> "Users" (caption from uba_use.meta)
 UB.i18n('uba_user.firstName') // -> "First Name" (caption of uba_user.firstName attribute)

 UB.i18n('uba_user.name#description') // "User login in lower case" (description for uba_user.name attribute)
 UB.i18n('uba_audit#documentation') // "All changes to UBA..." ( documentation for uba_audit entity )
 UB.i18n('uba_audit#captionSingular') // "Security Audit" ( fallback to caption because uba_audit.captionSingular is not defined in meta)
  

# iso8601ParseAsDate static

Convert UnityBase server date response to Date object. date response is a day with 00 time (2015-07-17T00:00Z), to get a real date we must add current timezone shift

# LDS_KEYS static

localDataStorage keys used by @unitybase-ub-pub (in case of browser environment)

# LocalDataStore : LocalDataStore static

Helper class for manipulation with data, stored locally in (TubCachedData format)

# MD5 static

Calculate MD5 checksum

# SHA256 static

Calculate SHA256 checksum

# truncTimeToUtcNull static

Convert a local DateTime to Date with zero time in UTC0 timezone as expected by UB server for Date attributes

# UBAbortError : UBAbortError static

Quiet exception. Global error handler does not show this exception for user. Use it for silently reject promise.

# UBCache : UBCache static

Client side cache

# UBError : UBError static

Client-side exception. Such exceptions will not be showed as unknown error in UB.showErrorWindow

# UBNativeMessage : UBNativeMessage static

Class for communicate with native messages plugin content script.

Methods

# apply (objectToobject, objectsFromobject) → object static

Copies all the properties of one or several objectsFrom to the specified objectTo. Non-simple type copied by reference!

Return:

returns objectTo

Arguments:
  • objectTo: object

    The receiver of the properties

  • objectsFrom: object

    The source(s) of the properties

# base64FromAny (dataFile) → Promise.<string> static

Fast async transformation of data to base64 string

Return:

resolved to data converted to base64 string

Arguments:

# base64toArrayBuffer (base64string) → ArrayBuffer static

Convert base64 encoded string to decoded array buffer

Arguments:

# booleanParse (v*) → boolean | null static

Convert UnityBase server Boolean response (0 or 1) to JS Boolean (false or true)

Arguments:
  • v: *

    Value to convert

# connect (cfgobject) → Promise.<UBConnection> static

Create authorized connection to UnityBase server.

For a browser clients in case value of silenceKerberosLogin localStorage key is 'true' and 'Negotiate' authorization method is enabled for application will try to authenticate user using Kerberos/NTLM method.

Preferred locale tip: to define connection preferredLocale parameter call localStorage.setItem(UB.LDS_KEYS.PREFERRED_LOCALE, 'uk') before call to UBConnection.connect

Arguments:
  • cfg: object
    • hoststring

      Server host

    • pathstring

      API path - the same as in Server config httpServer.path

    • onCredentialRequiredauthParamsCallback

      Callback for requesting a user credentials. See authParamsCallback description for details

    • onRequest2farequest2faCallback

      Callback for requesting second factor (if needed). See request2faCallback description for details

    • allowSessionPersistentboolean

      For a non-SPA browser client allow to persist a Session in the local storage between reloading of pages. In case user logged out by server side this type persistent not work and UBConnection will call onCredentialRequired handler, so user will be prompted for credentials

    • onAuthorizationFailfunction

      Callback for authorization failure. See event:authorizationFail event. Should handle all errors inside!

    • onAuthorizedfunction

      Callback for authorization success. See event:authorized event. On this stage connection.domain is still not exists, so do not use Repository inside.

    • onNeedChangePasswordfunction

      Callback for a password expiration. See event:passwordExpired event

    • onGotApplicationConfigfunction

      Called just after application configuration retrieved from server. Accept one parameter - connection: UBConnection Usually on this stage application inject some scripts required for authentication (locales, cryptography etc). Should return a promise then done

    • onGotApplicationDomainfunction

      Called after server returns domainInfo and connection.domain is initialized

  
      const UB = require('@unitybase/ub-pub')
let conn = UB.connect({
  host: window.location.origin,
  path: window.location.pathname,
  onCredentialRequired: function(conn, isRepeat){
      if (isRepeat){
          throw new UB.UBAbortError('invalid credential')
      } else {
          return Promise.resolve({authSchema: 'UB', login: 'admin', password: 'admin'})
      }
  },
  onAuthorizationFail:  function(reason){
      alert(reason)
  }
})
conn.then(function(conn){
  conn.get('stat').then(function(statResp){
    document.getElementById('ubstat').innerText = JSON.stringify(statResp.data, null, '\t')
  })

  conn.Repository('ubm_navshortcut').attrs(['ID', 'code', 'caption']).selectAsArray().then(function(data){
    let tmpl = _.template(document.getElementById('repo-template').innerHTML);
    let result = tmpl(data.resultData);
    // document.getElementById('ubnav').innerText = JSON.stringify(data.resultData);
    document.getElementById('ubnav').innerHTML = result;
  })
})
  

# file2Uint8Array (fileFile) → Promise.<Uint8Array> static

Fast async transformation of file to Uint8Array

Return:

resolved to file content as Uint8Array

Arguments:
  
      let f = document.getElementById('inputOfTypeFile').files[0]
 ui8Arr = await UB.file2Uint8Array(f)
  

# format (stringToFormatstring, values*) → string static

Allows to define a tokenized string and pass an arbitrary number of arguments to replace the tokens. Each token must be unique, and must increment in the format {0}, {1}, etc. Example usage:

Return:

The formatted string.

Arguments:
  • stringToFormat: string

    The string to be formatted.

  • values: *

    The values to replace tokens {0}, {1}, etc in order.

  
      var s = UB.format('{1}/ext-lang-{0}.js', 'en', 'locale');
// s now contains the string: ''locale/ext-lang-en.js''
  

# get (urlstring, configoptobject) → Promise.<XHRResponse> static

Shortcut for UB.xhr to perform a GET request

Arguments:
  • url: string

    Relative or absolute URL specifying the destination of the request

  • config: object

    Optional configuration object as in UB.xhr

  
      // GET http://my-api.com?param=val
const resp = await UB.get('http://my-api.com', {params: {param: 'val'}})
console.log(resp.data)
  

# i18nExtend (localizationObjectobject) static

Merge localizationObject to UB.i18n. Usually called form modelFolder/locale/lang-*.js scripts

Arguments:

# inject (urlstring, charsetoptstring) → Promise static

Inject external script or css to DOM and return a promise to be resolved when script is loaded.

if script successfully loaded using inject it will not be loaded anymore with repeatable calls to UB.inject.

Arguments:
  
      //Load script.js:
UB.inject('jslibs/script.js')

//Load several script at once and error handling:
Promise.all([UB.inject('jslibs/script.js'), UB.inject('script2.js')])
.catch(function(err){
 console.log('Oh! error occurred: ' + err)
})

//Load one script and then load other
UB.inject('jslibs/js_beautify.js').then(function(){
 console.log('first script loaded. Continue to load second')
 return UB.inject('jslibs/js_beautify1.js')
})

//Load a couple of resources:
Promise.all([UB.inject('css/first.css'), UB.inject('css/second.css')])
  

# iso8601Parse (valueDate) → Date static

Convert UnityBase server dateTime response (ISO8601 string) to Date object

Arguments:
  • value: Date| string

    String representation of Date in ISO8601 format

# LocalRepository (localDataArray | TubLocalData, entityNamestring) → LocalRepository static

Create a new instance of a repository based on a local data

Arguments:
  
      const UB = require('@unitybase/ub-pub')
 const localData = {data: [[1, 'Jon'], [2, 'Bob']], fields: ['ID', 'name'], rowCount: 2}
 await UB.LocalRepository(localData, 'uba_user').attrs('name').where('ID', '=', 2).selectScalar() // "Bob"
  
  
      const UB = require('@unitybase/ub-pub')
 const localData = [{ID: 1, name: 'Jon'}, {ID: 2, name: 'Bob'}]
 await UB.LocalRepository(localData, 'uba_user').attrs('ID', 'name').selectAsArray()
  

# logError (msg*) static

Log error message to console (if console available)

Arguments:
  • msg: *

# logWarn (msg*) static

Log warning message to console (if console available)

Arguments:
  • msg: *

# ns (namespacePathstring) → object static deprecated

Try to avoid namespaces - instead create a module and use require()

Creates namespaces to be used for scoping variables and classes so that they are not global.

Return:

The namespace object.

Arguments:
  
      UB.ns('DOC.Report')
DOC.Report.myReport = function() { ... }
  

# post (urlstring, data*, configoptobject) → Promise.<XHRResponse> static

Shortcut for UB.xhr to perform a POST request

Arguments:
  • url: string

    Relative or absolute URL specifying the destination of the request

  • data: *

    Request content

  • config: object

    Optional configuration object as in UB.xhr

  
      // POST http://my-api.com?param1=12&param2=someVal with body contains a stringified object
const resp = await UB.post('http://my-api.com', {this: 'is', body: 'of' request}, {params: {param1: 12, param2: 'someVal'}})
console.log(resp.data)
  

# Repository (entityCodeOrUBQLstring) → ClientRepository static

Create a new instance of repository for a current connection. To be used after connection is created.

Arguments:
  • entityCodeOrUBQL: string| object

    The name of the Entity for which the Repository is being created or UBQL

# setErrorReporter (errorReportedFunctionfunction) static

Set an error reporter callback for unhandled errors (including unhandled promise rejections). Callback signature function({errMsg, errCode, entityCode, detail})

  • errMsg is already translated using UB.i18n

This callback also called inside UBPub.showErrorWindow

Arguments:

# showErrorWindow (errMsgstring | object | Error | UBError, errCodeoptstring, entityCodeoptstring, detailoptstring) static

Default error reported handler. Will translate error message using i18n.

For a UI other then adminUI developer can call UB.setErrorReporter to set his own error reporter

Arguments:
  
      const UB = require('@unitybase/ub-pub')
  const vm = new Vue({
    ...
    methods: {
      showError: function(errMsg, errCode, entityCode, detail) {
        this.$message({
          showClose: true,
          message: errMsg,
          type: 'error'
        })
      }
      ...
  })
  UB.setErrorReporter(vm.showError.bind(vm))
  

# xhr (requestConfigobject) → Promise.<XHRResponse> static

An asynchronous HTTP request. Returns a Promise, what resolves to the XHRResponse object:

Arguments:
  • requestConfig: object
    • urlstring

      Absolute or relative URL of the resource that is being requested

    • methodstring

      HTTP method (e.g. 'GET', 'POST', etc). Default is GET

    • paramsObject.<(string | object)>

      Map of strings or objects which will be turned to ?key1=value1&key2=value2 after the url. If the value is not a string, it will be JSONified. Keys and values are URL encoded inside a function.

    • datastring | object

      Data to be sent as the request message data

    • headersobject

      Map of strings or functions which return strings representing HTTP headers to send to the server. If the return value of a function is null, the header will not be sent. Merged with UB.xhrDefaults.headers

    • transformRequestfunction | Array.data, function())>

      Transform function or an array of such functions. The transform function takes the http request body and headers and returns its transformed (typically serialized) version.

    • transformResponsefunction | Array.data, function())>

      Transform function or an array of such functions. The transform function takes the http response body and headers and returns its transformed (typically deserialized) version.

    • timeoutnumber | Promise

      timeout in milliseconds, or {Promise} that should abort the request when resolved. Default to {UB.xhrDefaults.timeout}

    • withCredentialsboolean

      whether to to set the withCredentials flag on the XHR object. See requests with credentials for more information.

    • responseTypestring

      see responseType.

    • onProgressfunction

      XHR onProgress callback, see ProgressEvent for details. To be user instead obsolete Q Promise.progress()

    Object describing the request to be made and how it should be processed. The object has the following properties:

  
      //Get some data from server:
const resp = await UB.xhr({url: 'getAppInfo'})
console.log('app info: %o', resp.data)

//The same, but in more short form via `UB.get` shorthand:
const resp = await UB.get('getAppInfo')
console.log('app info: %o', resp.data)

//Run POST method:
UB.post('ubql', [
  {entity: 'uba_user', method: 'select', fieldList: ['*']}
]).then(function(resp) {
  console.log('success!')
}, function(resp) {
  console.log('request failed with status' + resp.status);
})

//retrieve binary data as ArrayBuffer
const resp = await UB.get('downloads/cert/ACSK(old).cer', {responseType: 'arraybuffer'})
console.log('Got ArrayBuffer of %d byte length', resp.data.byteLength);
  

# addBrowserUnhandledRejectionHandler () inner

Intercept all unhandled errors including Promise unhandled rejections. Errors will be parsed and passed to UB.showErrorWindow {@see setErrorReporter setErrorReporter}