Main Concepts

Components

Components are the basic building blocks of any Obvia app and are based on bootstrap. A component is a JavaScript class that accepts properties (props) and returns an element which describes how a section of the UI should appear. Components can have additional features, such as the ability to contain logic, and can be easily and dynamically customized by editing the properties.

Binding

Binding in Obvia allows programmers to apply changes to the properties of context objects directly into the properties of the components. This means that whenever the properties of the context object change, so do the properties in the component. Each Obvia component handles its own property bindings. Programmers just need to use the property name enclosed in curly brackets.

Example:

component: {
        constructor: Button,
        props: {
            id: 'button',
            type: "button",
            value: "{id}",
            label: "{text}",
            classes: "{buttonClass}",
            style: "float: left; border-radius: 0px",
            click: clickTest
        }
    }

Additional information:

  • The default context object is ‘window’, but we may override it by setting bindingDefaultContext property.
  •  In special components like Repeater, DataGrid, and its derived components, the default context object is the currentItem of the dataProvider.
  • Complex expressions can be used as binding expressions. The property of the component will be updated if one of the members of the expression (part of the context object) is updated.
  • We may refreshBindings, getBindingExpression, setBindingExpression etc.
  •  For binding on arrays we have to use ArrayEx.

JTemplate

As the name suggests, JSON Template allows programmers to process the JSON data returned from an Ajax call. JTemplate provides the ability to iterate of a set of data (such as an array of objects), and format that data for display to the user. While writing the application, the templates do not require any server side functionality to actually display the data.

var b = new JTemplate("{\"obj\":{{hello.world}}}");
window.hello = {};
window.hello.world = "Hey There";
var obj = b.parse();

Will print:
{obj: “Hey There”}

Cache

The main purpose of caching is improving the speed and performance of the application through client-side storage. By storing data on the browser itself, users skips fetching information from the server every time they need it.

Asynchronous  persistent data structures in Cache.js are very easy. Cache persist doesn’t use a database; instead, JSON documents are stored in the file system for persistence. Since there is no network overhead, this is just as fast as a database can get.

Some Functions used are:

  • get(): retrieves a value for a given key/name. If the value isn’t cached, it returns null.
  • clear(): deletes all the stored keys/names.
  • set(): stores a value. If the time isn’t passed in, it is stored for 300000 msec.

Remote Object

This library makes it possible for objects to communicate asynchronously between memory-isolated JavaScript contexts. Promises serve as proxies for remote objects. The benefit to using the asynchronous API when interacting with remote objects is that you can send chains of messages to the promises that the connection makes.

  • We use cacheProps as we might want to later persist/send this to the server for inspection/statistics.
  • If autoretry is true, the system will automatically retry to get data in exponential time intervals.
var r = new  RemoteObject({getData_Action:"https://jsonplaceholder.typicode.com/posts", cacheProps:{enabled:true}});
r.getData().then(function(result){console.log(result)});
.catch()

Behavior

We use the term ‘behavior’ to express how our App handles different events.

  • Different events can expose the same behavior. A filter function can be used to test if an event will expose a behavior.
  • The same event can expose many behaviors. Through filter functions, we can control which behaviors will be exposed and which will not.

The component (EventDispatcher in General) that exposes a given behavior is called a “manifestor”. Judging from the above definition of behaviors, it can be concluded that we can handle both event muxing and demuxing scenarios.

A behavior is conceptually compound by the behavior definition e.g. the event, and the behavior(s) filter function relation:

Example:

let waBehaviors = {
        "click": "BECOME_ACTIVE",
        "mousedown": "WA_PREVENT_DRAGSTART",
        "mouseover": "WA_HOVER",
        "mouseout": "WA_HOVER",
        "mousemove": {
            "IS_WA_RESIZE_NS": isMouseMoveNS
        },
        "resize": "WA_RESIZE",
        "contextmenu": "WA_REMOVE",
        "drop": "ADD_COMPONENT",
        "dragover": "ALLOW_DROP",
    };

Behavior implementations:

  • Click: clicked area becomes active.
  • Keydown: determines an undo or a redo action.
  • WA_UNDO: key combination for Undo.
  • WA_REDO: key combination for Redo.
  • SPLIT_HOR: splits the selected container horizontally.
  • SPLIT_VERT: splits the selected container vertically.
  • BECOME_ACTIVE: the Container becomes active. This will hold the instance of the component which manifested this behavior (the manifestor).
  • WA_HOVER: the container is hovered here.
  • WA_RESIZE_NS: indicates a bidirectional resize container vertically.
  • WA_RESIZE_EW: indicates a bidirectional resize Container horizontally.
  • WA_REMOVE: removes Container.
  • SAVE_LAYOUT: saves the created layout in JSON.
  • Mousemove: determines a WA_RESIZE_NS behavior.

Application & Applets

It is easy for complex single page frontend applications to become unmaintainable without a proper structure. We need to properly and uniformly:

  • handle events (components and browser url changes)
  • pass data to UI views
  • handle API calls
  • persist (and load) application states

App.js represents the application itself and inherits from Container.js. It keeps the list of behaviors and routes events to the proper behavior implementation.

An application hosts Applets. An Applet is a self-contained visual and functional unit of the application. When an applet is initialized it will:

  • load the JSON literal for the view
  • load the JS where the behavior implementations are contained and pass the context to it
  • register behavior implementations in the App
  • recursively load all dependencies (children applets)

Additional information:

App.js monitors url hash changes by using the BrowserManager.js singleton and initializes the proper applet – the one that is anchored at the new url.

App.js keeps track of history of manifested behaviors.

App.js itself manifests default behaviors:

  • APP_ACTIVE, APP_INACTIVE, APP_WINDOW_SHOWN, APP_WINDOW_HIDDEN, APP_UNLOADED
  • BEGIN_DRAW, END_DRAW
  • HISTORY_UNDONE, HISTORY_REDONE, HISTORY_STEP_ADDED

Utility Classes: String

Some of the most important functions that can be used in this class of StringUtils are:

  • isString(): checks whether or not the passed value is a string.
  • guid(): returns a uuid, which is an identifier with a certain uniqueness guarantee. It is used as a property in the Repeater Component.
  • soundex(): a function that can index names by sound. The algorithm encodes consonant, so a vowel will not be encoded unless it is the  first letter.
  • truncate(): truncates a string if it is longer than expected. By default, it ends with a … ellipsis sequence.
  • hashCode(): generates a hash from string.
  • occurrences(): counts the occurrences of substring in a string.
  • getEditDistance(): this function is a Levenshtein Distance algorithm, and measures the distance between two strings. The Levenshtein Distance between two strings is the minimum number of single-character edits (insertions, deletions or substitutions)  required to change one string to another. For example, if we wanted to change “cat” to “cot”, we would only need to replace the “a” with “o”, therefore it would be 1 replacement. Levenshtein Distance is 1.

Utility Classes: Arrays

Some of the most important functions that can be used with arrays are:

  • getMatching(): finds and returns  a valid matchingValue parameter  for an Object or instance of Array.
  • intersect(): returns  the intersecting elements of two arrays.
  • intersectOnKeyMatch(a, b, key): returns the intersection elements of two arrays when the key field to match is given.
  • Array.prototype.last = function(): retrieves the last element in an array, as the JavaScript Array class does not offer the possibility.
  • dedupe(): removes the duplicate values from an array.
  • includes(): determines whether an array includes a certain value among its entries, returning true or false as a result. It uses the sameValueZero Algorithm to determine whether the given element is found.
  • splicea(): the known splice() method changes the contents of an array by removing or replacing existing elements and/or adding new elements. On the other hand, splicea() does the same thing as splice(), but takes an array of items other than comma separated items.
  • equal(): returns true if the arrays are equal. Beware though, two different object instances will never be equal; for example : (this[i] != array[i])   {x:20}!={x:20}

Utility Classes: Objects

Some of the most important functions that can be used with objects are:

  • copyAccessors(): copies getters and setters.
  • Object.copy(): works like Object.assign but includes getters, setters, and non enumerable properties too.
  • extend(): pass in the objects to merge as arguments. For a deep extend, set the first argument to `true`. For copying accessors (getters and setters) set the second argument to true, otherwise they will be skipped. If you want to skip deep copying for a specified property pass it as a third string or array parameter – this is useful for mimicking static variables.
  • merge(): merges the object into the extended object. If there is a deep merge and the property is an object, then it merges properties.
  • minifyArray(): returns an object with column names and raw data rows. If the columns are not specified, all members of each object will be taken instead.
  • deminifyArray(): normalizes the minified array of {columns[],rows[]} format to an array of objects.

Roadmap

This is a basic roadmap with some instructions to keep in mind when working with Obvia.

  • The implementation class is to be named according to the applet anchor
  • Implement the dependency injection
  • Create a dependency info-structure
  • Create a base-dependent type to return dependencies-related information (array of dependencies info-structure)
  • Implementations will be dependent types
  • Create a Factory Implementation to support the autowiring of dependencies
  • Create the info structure for the types which the Factory will create/return instances of
  • An Applet will not create the implementation instance directly but will ask the Factory for it
  • The Applet (& App) should handle URL hash changes via an external dependency (the existing logic is to be wrapped in a default provider)
  • The security is to be provided by external dependencies