slingjs

Client-side JavaScript framework for Single Page Applications

Usage no npm install needed!

<script type="module">
  import slingjs from 'https://cdn.skypack.dev/slingjs';
</script>

README

Sling update history

Sling

Sling is a client-side JavaScript framework for building Single Page Applications (SPAs). Sling is lightweight, 16KB minified, and less than 5KB gzipped.

Sling creates and uses an Incremental DOM to perform differential updates for fast rendering.

Sling has an automatic change detection mechanism which updates your components for you.

Sling is structured using ECMAScript modules so that Sling code is tree shakeable to ultimately reduce bundle sizes.

See: slingjs.org

Goals

Next Billion Users (NBUs) Empower developers to create SPAs for the NBUs of the web. The NBUs tend to use more affordable and less powerful devices. These devices struggle to achieve a two second Time to Interactive (TTI) with larger component libraries and frameworks.

Practical Familiarity with other JavaScript component libraries. Components are instantiated objects which may be used to control their own state. Components have a simple markup language with a gradual learning curve.

Generalized API as unopinionated as possible. Developers choose the right design patterns for their SPAs—not the library.

Fast High performance. Sling aims to get your SPA to interactive as quickly as possible and aims to keep your SPA as responsive as possible by staying within small code production budgets. With Sling, it should be easier for your SPA to run at 60 frames per second for a native application experience.

Minimal Setup Simply import the Sling functions required by your SPA and Sling is ready to use. No configuration files and no hidden requirements.

Testing

Run npm run devServer after a npm install to start webpack-dev-server.

Then navigate to localhost:8080/todo.html.

Performance (Time)

Because Sling is so lightweight, it can render thousands more nodes than Angular can in the same time period. Both test applications were served using the local-web-server NPM package.

Version Number of nodes created Average time Nodes per ms
Sling Core 10.1.0 1,000 37.195ms 26.885
Mithril.js 2.0.4 1,000 39.312ms 25.437
Angular 10.1.6 1,000 165.810ms 6.030

Sling.js creates nodes 8.173 times faster than Angular.

The above nodes were <p> tags containing strings generated by the following simple function:

for (let i = 0; i < 1000; ++i) {
    const value = i / 100;
    let str = val.toString(36).substring(7);
    this.data.push(str);
}
Version Number of nodes changed Average time Nodes per ms
Sling Core 10.1.0 1,000 17.197ms 58.149
Mithril.js 2.0.4 1,000 23.600ms 42.372
Angular 10.1.6 1,000 362.536ms 2.758

Sling.js changes nodes 21.263 times faster than Angular in automatic change detection mode.

The above nodes were <p> tags containing strings generated by the following simple function:

for (let i = 0; i < 1000; ++i) {
    const value = i / 50;
    let str = val.toString(36).substring(4);
    this.data.push(str);
}

Performance (Network)

Version Requests Total
Sling Core 10.1.1 1 7.9KB
Mithril.js 2.0.4 1 26.7KB
Angular 10.1.6 3 143.00KB

Add Sling

To add Sling to your project, simply import the Sling function required by your application.

Below is an example of Sling import statements:

import { setState, mount, setDetectionStrategy, addRoute } from './sling';
import { Observable } from './sling-reactive';
import { slGet } from './sling-xhr';

Compatibility

Sling uses ES2015/ES6 syntax. Sling does not have any production dependencies.

Components

A component is a JavaScript class with a view() function that returns markup to render.

Components may be nested, but lifecycle hooks for nested components will not be automatically called. This is done for performance reasons and to stay within production code budgets.

Example component:

class HelloWorldComponent {
    constructor() {
    }

    view() {
        return markup('h1', {
            children: [
                textNode('Hello, world!')
            ]
        });
    }
}

Change Detection

Sling supports two change detection strategies: automatic and manual. The default mode is automatic.

Strategy Description
s.CHANGE_STRATEGY_AUTOMATIC Automatically update components after browser events and requests. This is the default setting.
s.CHANGE_STRATEGY_MANUAL Manually update components after browser events and requests.

Automatic change detection performs updates upon the following:

  • All browser events (click, mouseover, keyup, etc.)
  • setTimeout and setInterval
  • XMLHttpRequest and Fetch API requests

Automatic change detection does not perform updates upon the following:

  • Websocket events
  • IndexedDB callbacks

For versions of setTimeout and setInterval that do not trigger automatic change detection, use the following:

  • s.DETACHED_SET_TIMEOUT()
  • s.DETACHED_SET_INTERVAL()

For example:

s.DETACHED_SET_TIMEOUT(() => {
    console.log('Hello, world!');
}, 0);

Lifecycle Hooks

Components may specify up to three lifecycle hooks: |Lifecycle Hook |Triggers Change Detection|Timing | |-----------------------|-------------------------|-------------------------------------------| |slOnInit() |false |Before the component's view function is called and before the component is mounted to the DOM.| |slOnDestroy() |false |Before the component is removed from the DOM.| |slAfterInit() |true |After the component is mounted to the DOM.|

Lifecycle hooks are executed for nested components returned by a component's view function.

class NestedHookComponent {
    slOnInit() {
        console.log('Will be called after root component slOnInit hook.');
    }

    view() {
        return markup('div', {
            children: [
                textNode('Child component.')
            ]
        });
    }
}

class RootComponent {
    slOnInit() {
        console.log('Will be called before view function of component is called.');
    }

    view() {
        return markup('div', {
            children: [
                textNode('Root component.'),
                new NestedHookComponent()
            ]
        });
    }
}

Directives

Structural directives modify interactions with the DOM layout.

Directive Type Behavior
useexisting Structural Create the element or, if it exists, use the existing element.
onlychildren Structural Only perform change detection on element's children.
onlyself Structural Only perform change detection on the element and not children.
trustchildren Structural Render HTML string children.
slfor Structural Render a named list using a node factory and an update function.
slfornamed Structural Render a named list using a node factory and an update function. This directive may be used instead of slfor where function names are minified in builds.

Attribute directives change the appearance or behavior of a DOM element.

Directive Type Behavior
slanimatedestroy Attribute Wait for CSS class animation to finish before removal from the DOM.

Example directive usage:

view() {
    return markup('div', {
        attrs: {
            id: 'divSheetContent'
        },
        children: [
            new SelectedPartHeaderComponent().view(),
            markup('div', {
                attrs: {
                    id: 'chartDiv',
                    sldirective: 'useexisting',
                    style: 'width: 90vw;'
                }
            })
        ]
    })
}

Another example of directive usage:

view() {
    return markup('div', {
        attrs: {
            id: 'divnav',
        },
        children: [
            textNode('Select a route to navigate to.'),
            markup('button', {
                attrs: {
                    onclick: this.navigateToHydrate.bind(this)
                },
                children: [
                    textNode('Hydrate Route')
                ]
            }),
            markup('button', {
                attrs: {
                    onclick: this.navigateToRoot.bind(this)
                },
                children: [
                    textNode('Root Route')
                ]
            }),
            markup('div', {
                attrs: {
                    sldirective: 'trustchildren'
                    },
                children: [
                    textNode(this.ssrContent)
                ]
            })
        ]
    })
}

Another example of directive usage:

view() {
    return markup('div', {
        attrs: {
            ...this.showhide !== true && { class: 'visible' }
        },
        children: [
            ...(this.hide === false ? [
                markup('h1', {
                    attrs: {
                        slanimatedestroy: 'hide'
                    },
                    children: [
                        textNode('Hello, world!'),
                        markup('button', {
                            attrs: {
                                onclick: this.hideTemplate.bind(this)
                            },
                            children: [
                                textNode('Hide')
                            ]
                        })
                    ]
                })
            ] : [
            ])
        ]
    });
}			

Example of ``slfor``` directive usage:

export class TestRenderElement3 {
    constructor() {
        this.data = function () { return Store3.data; };
        this.selected = function () { return Store3.selected; };
        this.run = function () {
            Store3.run();
        };
        this.add = function () {
            Store3.add();
        };
        this.update = function () {
            Store3.update();
        };
        this.select = function (id) {
            Store3.select(id);
        };
        this.delete = function (id) {
            Store3.remove(id);
        };
        this.runLots = function () {
            Store3.runLots();
        };
        this.clear = function () {
            Store3.clear();
        };
        this.swapRows = function () {
            Store3.swapRows();
        };

        this.add();
    }

    updateRow(ctx, v) {
        if (this.$id === undefined) {
            this.$fid = this.childNodes[1];
            this.$label = this.children[2].childNodes[0];
        }

        this.children[2].children[0].onclick = wrapWithChangeDetector(ctx.delete.bind(this, v.id));

        if (this.$label.childNodes[0].data !== v.label) {
            this.$label.removeChild(this.$label.childNodes[0]);
            this.$label.append(v.label);
        }
    
        const idStr = String(v.id);
        
    if (this.$id.childNodes[0].data !== idStr) {
            this.$id.removeChild(this.$foo.childNodes[0]);
            this.$id.append(v.id);
        }
    
        var className = (v.id === ctx.selected()) ? 'danger' : ''
        if (this.className !== className) this.className = className
    }

    makeRow(d) {
        return markup('tr', {
            attrs: {
                ...d.id === this.selected() && { class: 'danger' },
                onclick: this.select.bind(this, d.id),
                onremove: this.delete.bind(this, d.id)
            },
            children: [
                new TestRenderElement4(),
                markup('td', {
                    attrs: {
                        'class': 'col-md-1'
                    },
                    children: [
                        textNode(d.id)
                    ]
                }),
                markup('td', {
                    attrs: {
                        'class': 'col-md-4',
                    },
                    children: [
                        markup('a', {
                            attrs: {
                                'href': '#',
                                onclick: this.select.bind(this, d.id)
                            },
                            children: [
                                textNode(d.label)
                            ]
                        })
                    ]
                }),
                markup('td', {
                    attrs: {
                        'class': 'col-md-1',
                    },
                    children: [
                        markup('a', {
                            attrs: {
                                'href': '#',
                                onclick: this.delete.bind(this, d.id)
                            },
                            children: [
                                markup('span', {
                                    attrs: {
                                        'class': 'glyphicon glyphicon-remove',
                                        'aria-hidden': 'true'
                                    }
                                })
                            ]
                        })
                    ]
                }),
                markup('td', {
                    attrs: {
                        'class': 'col-md-6'
                    }
                })
            ]
        });
    }

    view() {
        return markup('div', {
            attrs: {
                'class': 'container',
                'id': 'rendertoelement3'
            },
            children: [
                markup('table', {
                    attrs: {
                        'class': 'table table-hover table-striped test-data'
                    },
                    children: [
                        markup('tbody', {
                            attrs: {
                                'slfor': 'myfor:data:makeRow:updateRow'
                            }
                        })
                    ]
                })
            ]
        });
    }
}

Core API

setState

void setState ( newStateObj )

Set a new state object for SPA.

getState

object getState ( )

Get the state object for SPA.

markup

object markup ( tagString, { attrs: {}, children: [] } )

Returns markup object to render. May be mounted to DOM.

Example markup call:

markup('div', {
    attrs: {
        style:  "width:50%;margin:auto;padding:1rem;"
    },
    children: [
        ...Array.from(getState().getNotes(), (note) =>
            markup('div', {
                attrs: {
                    class:  'input-group mb-3 animEnter',
                    style:  'width:100%;'
                },
                children: [
                ]
            })
        )
    ]
});

m

object markup ( tagString, { attrs: {}, children: [] } )

Terse alias for markup() function.

textNode

string textNode( text )

Append a DOMString to a node.

Example textNode call:

textNode('Click me!');

mount

element mount ( rootElementId, component, attachDetector = true )

Mounts component on element with ID rootElementId in DOM. Returns root element in DOM where component was added.

Mounted components replace the element with rootElementId to avoid an excessive DOM size. Mounted components must have the same root element ID as the element in the DOM they are attached to.

By default, the Sling change detector is attached for the mounted component. Setting attachDetector to false prevents the change detector from being attached to this component. There are two convenience constants for change detection which are as follows:

Constant Value
s.CHANGE_DETECTOR_DETACHED false
s.CHANGE_DETECTOR_ATTACHED true

update

void update ( rootElementId, component )

Updates the component mounted at element with ID rootElementId.

renderElement

HTMLElement renderElement ( { tagName, attrs, children }, isDetached = false )

Render a DOM node from markup.

const node = renderElement(markup('p', { children: [ textNode('Hello, world!') ] }));

Set isDetached to true if the created DOM node will not be attached to the DOM and managed by Sling.js.

version

string version( )

Returns Sling version number represented as a string.

Example:

console.log(version()); // '10.1.1'

resolveAll

object resolveAll( promiseArray )

Returns an object with data about settled Promises in the format:

{ result: Promise Result | null, error: Error | null, status: 'fulfilled' | 'rejected' }

Example:

const requestPromises = [
    fetch('todo.html'), 
    fetch('http://does-not-exist')
];

resolveAll(requestPromises).then((results) => {
    const successfulPromises = results.filter(p => p.status === 'fulfilled');
});

hydrate

element hydrate ( rootElementId, attachDetector = true )

Attach event handlers to component on element with ID rootElementId in DOM. Returns root element in DOM.

Sling will take over the static HTML sent by the server and manage change detection.

In order to correctly hydrate static HTML sent by the server, the static HTML of the root element must contain two attributes. First, an ID must be specified so Sling can locate the component for hydration. Second, the attribute slssrclass must be specified. The value of slssrclass should be the class name of the view which manages the component. The class identified by slssrclass should be defined on the window object, or defined on the value of this.

Example:

class TestSsrHydrateComponent1 {
    hydratedFunction() {
        const state = getState();
        state.ishydrated = true;
        setState(state);
    }

    view() {
        const state = getState();
        const isFuncCalled = state.ishydrated;

        return markup('div', {
            attrs: {
                id: 'testssrhydrate',
                slssrclass: 'TestSsrHydrateComponent1'
            },
            children: [
                markup('button', {
                    attrs: {
                        id: 'ssrTest2',
                        onclick: this.hydratedFunction.bind(this)
                    },
                    children: [
                        textNode('Test Hydrate')
                    ]
                }),
                markup('div', {
                    attrs: {
                        id: 'ssrTest1'
                    },
                    children: [
                        ...(isFuncCalled === true ? [
                            textNode('Hydrated function called.')
                        ] : [
                            textNode('SSR placeholder.')
                        ])
                    ]
                })
            ]
        })
    }
}
window.TestSsrHydrateComponent1 = TestSsrHydrateComponent1;

hydrate('testssrhydrate');

By default, the Sling change detector is attached for the mounted component. Setting attachDetector to false prevents the change detector from being attached to this component. There are two convenience constants for change detection which are as follows:

Constant Value
s.CHANGE_DETECTOR_DETACHED false
s.CHANGE_DETECTOR_ATTACHED true

renderToString

string renderToString( component )

Renders a component into a HTML string.

Example:

const compStr = renderToString(new LoginComponent());

slFor

Below is an example of slFor structural directive usage.

attrs: {
    'slfor': 'myfor:data:makeRow:updateRow'
}

Arguments:

  1. Name of list (must be unique)
  2. The data list or function to retrieve data list. Must be a property of the enclosing class.
  3. The node factory. May return DOM node or Sling markup.
  4. The node update function.

Node Factory

The node factory receives a list item as the first argument.

Below is an example node factory function:

makeRow(listItem) {
    return renderElement(markup('p', { children: [ textNode(listItem.id) ] }));
}

Node Update Function

The node update function receives a reference to the object which constructed the row as the first argument and a list item as the second argument. The current DOM node is referenced by this.

Below is an example of a node update function:

updateRow(context, listItem) {
    if (this.$label === undefined) {
        this.$label = this.children[0];
    }
    
    this.children[1].onclick = wrapWithChangeDetector(context.deleteRow.bind(this, listItem.id));
    
    if (this.$label.childNodes[0].data !== listItem.id) {
        this.$label.removeChild(this.$label.childNodes[0]);
        this.$label.append(listItem.id);
    }
}

Named slFor

For builds where function names are minified, the structural directive slfornamed may be used instead of slfor.

The structural directive slfornamed takes arguments which are slfor property values of functions belonging to an object. The slfor property is typically set on functions in the slOnInit lifecycle hook.

One difference between slfor and slfornamed is that for slfornamed every argument must reference a function identified by slfor. The data list can not be a simple property.

Below is an example of slfornamed usage.


class NamedSlForComponent1 {
    constructor() {
        this.list = ['a', 'b', 'c'];
    }

    slOnInit() {
        this.makeRow.slfor = 'make';
        this.updateRow.slfor = 'update';
        this.getData.slfor = 'data';
    }

    getData() {
        return this.list;
    }

    makeRow(data) {
        return markup('p', {
            children: [
                textNode(data)
            ]
        });
    }

    updateRow(context, data) {
        if (this.childNodes[0].data !== data) {
            this.removeChild(this.childNodes[0]);
            this.append(data);
        }
    }

    view() {
        return markup('div', {
            attrs: {
                id: 'divnamedslfor1'
            },
            children: [
                markup('div', {
                    attrs: {
                        'slfornamed': 'namedfor:data:make:update'
                    }
                })
            ]
        });
    }
}

Core Router API

addRoute

void addRoute ( hashUrlRegEx, { root: elementId, routeObj: object })

Define a hash-based route that will replace element with ID elementId's content with the specified component on route action.

Below is a list of possible routeObj properties:

Property Description
root The id of the element to replace on route.
component The component to replace root.
onCanDeactivate A function that returns true if the current route may be navigated away from. Called before onActivationCheck.
onActivationCheck A function that returns true if route action may be taken, otherwise false.
onActivationFail Object with route property to route to on onActivationCheck fail. Also may specify params and attachDetector.
onBeforeRoute Function to execute before taking route action. Called after onActivationCheck and before the route action is taken.

Example route definition:

addRoute('all', { component:  new  TodoListComponent(), root:  'divTodoList' });
addRoute('completed', { component:  new  TodoListCompletedComponent(), root:  'divTodoList' });
addRoute('user/:userId', { component: new UserProfileComponent(), root: 'divUserProfile' });
addRoute('.*', { component: new DefaultRouteComponent(), root: 'divRouterOutlet' });

Note: Use '.*' for the default route. Routes are checked in the order they are registered with addRoute.

Example onActivationCheck definition:

route('completed', { component:  new  TodoListCompletedComponent(), root:  'divTodoList', onActivationCheck: function(proposedRoute) { console.log('This will prevent route to \'completed\'.'); return false; }, onActivationFail: { route: 'all', params: { } } });

route

object route ( hashUrl, params = { }, attachDetector = true )

Navigate to the hash-based route according to a previously defined route. May specify route parameters as an object. Returns the component that was routed to.

By default, the Sling change detector is attached for the mounted component. Setting attachDetector to false prevents the change detector from being attached to this component.

Example route call:

route('user/5'); // Activates component at root for route 'user/:userId'

removeRoute

void removeRoute ( hashUrlRegEx )

Remove a route from the Sling router.

Example:

removeRoute('user/:userId');

getRoute

void getRoute ( )

Get the current hash-based route.

getRouteSegments

string[] getRouteSegments ( )

Returns the current hash-based route's segments or an empty array if there are none.

Example:

console.log(getRouteSegments()); // [ 'user', '5' ]

Using Sling Reactive, route changes may be listened to by using a Sling Observable. Every time the route changes, the subscribed function below will be called.

let routeObservable = Observable(getRouteSegments());
routeObservable.subscribe(function(routeArr) {
    if (routeArr.length > 0) {
        this.primaryRoute = routeArr[0];
    }
    else {
        this.primaryRoute = '';
    }
}.bind(this));

getRouteParams

object getRouteParams ( )

Returns the current route's parameters as an object. Returns { } if there are none.

Core Change Detection API

setDetectionStrategy

void setDetectionStrategy ( newDetectionStrategy )

Set the new change detection strategy.

detectChanges

void detectChanges ( eleId )

Trigger automatic change detection immediately. If eleId is undefined or null, change detection will be performed on all components.

isDetectorAttached

boolean isDetectorAttached ( eleId )

Returns true if Sling change detector is attached for the given element ID eleId.

detachDetector

void detachDetector ( eleId )

Detach the Sling change detector for the given element ID eleId.

wrapWithChangeDetector

function wrapWithChangeDetector ( funcToWrap )

Wrap a function funcToWrap with a change detector call, so every time the function is called change detection is also run.

XHR API

slRequest

Promise slRequest ( url, methodString, optionsObject = { } )

Create a XML HTTP Request (XHR) for the specified URL using the specified method, such as GET. Returns a Promise.

Request Option Default Detail
contentType application/json Set Content-Type request header.
body '' Body of the request.
withCredentials false Send cookies to 3rd party domains.
timeout 0 0 is no timeout. Specified in milliseconds.
headers {} Key/value request headers to set.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slRequestWithBody

Promise slRequestWithBody ( url, methodString, bodyObject = { } )

Create a XML HTTP Request (XHR) for the specified URL using the specified method, such as GET, with the specified body object. Returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slGet

Promise slGet ( url, data = { } )

Create a GET XHR request with the specified data which returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slPost

Promise slPost ( url, data = { } )

Create a POST XHR request with the specified data which returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slPut

Promise slPut ( url, data = { } )

Create a PUT XHR request with the specified data which returns a Promise. On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slPatch

Promise slPatch ( url, data = { } )

Create a PATCH XHR request with the specified data which returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

slDelete

Promise slDelete ( url, data = { } )

Create a DELETE XHR request with the specified data which returns a Promise.

On success, returns XMLHttpRequest which has data in response property like so:

XMLHttpRequest 
{
    onabort:  null
    onerror:  null
    onload:  null
    onloadend:  null
    onloadstart:  null
    onprogress:  null
    onreadystatechange:  ƒ ()
    ontimeout:  null
    readyState:  4
    response:  "[↵ {↵ "userId": 1,↵ "id": 1,↵ "title": ""
    ...
}

On request fail, returns an object in the following format:

{
    status: 404,
    statusText: ''
}

Reactive API

Stream

object Stream( )

Returns a Sling stream. A stream is a sequence of values over time and the associated operations which are automatically applied as those values change.

Example stream usage using Sling XHR API:

slGet('https://jsonplaceholder.typicode.com/posts').then(xhrResp => {
    let postArr = JSON.parse(xhrResp.response);
    let postStream = Stream().from(postArr).transform(function(arr) {
        return arr.filter(v => v.userId === 1);
    }).transform(function(arr) {
        return arr.filter(v => v.body.includes('quo'));
    });
});

Equivalent stream usage using preexisting stream object and Sling XHR API:

let postStream2 = Stream();
postStream2.transform(function(arr) {
    return arr.filter(v => v.userId === 1);
}).transform(function(arr) {
    return arr.filter(v => v.body.includes('quo'));
});

slGet('https://jsonplaceholder.typicode.com/posts').then(xhrResp => {
    let postArr = JSON.parse(xhrResp.response);
    postArr.forEach(post => {
        postStream2.push(post);
    });
});

Stream Functions

push

object push( value )

Push a value onto a stream. All transformers automatically called. Transformers are only applied on new data. Returns the stream.

transform

object transform ( function(arrayData) { } )

Add a new transformer to stream. Is automatically applied to all existing and new data. Returns the stream.

subscribe

object subscribe( function(arrayData) { } )

Add a function that is automatically called when the underlying stream data changes. Returns the stream.

getHasSubscription

boolean getHasSubscription( functionToCheck )

Check if functionToCheck is in the list of subscribed functions. Returns true if functionToCheck is in the list, false otherwise.

clearSubscription

object clearSubscription( functionToClear )

Remove functionToClear from the list of subscribed functions. Returns the stream.

clearSubscriptions

object clearSubscriptions( )

Remove all subscribed functions. Returns the stream.

call

object call ( function(arrayData) { } )

Call a function which operates on the stream's data. Returns the stream.

getData

[ ] getData( )

Returns a copy of stream array data.

clearTransformers

object clearTransformers( )

Clears all transformers acting on the stream. Data will remain in state of last transformation. Returns the stream.

from

object from ( newArray )

Set stream data to newArray and apply all existing transformers. Returns the stream.

Observable

object observable( array )

Returns a Sling observable. An observable is an array which may be listened to.

Example observable usage:

let myArray = [1, 2, 3];
let myObservable = Observable(myArray);
myObservable.subscribe(function(arr) {
    console.log('New length: ' + arr.length);
});

myObservable.getData().push(4);
obs.getData()[myObservable.getData().length] = 5;

Observable Functions

subscribe

void subscribe ( listenerFunction )

Listener function will be automatically called whenever the underlying array data changes. Returns the observable.

getHasSubscription

boolean getHasSubscription( functionToCheck )

Check if functionToCheck is in the list of subscribed functions. Returns true if functionToCheck is in the list, false otherwise.

clearSubscription

object clearSubscription( functionToClear )

Remove functionToClear from the list of subscribed functions. Returns the observable.

clearSubscriptions

object clearSubscriptions( )

Remove all subscribed functions. Returns the observable.

getData

[ ] getData( )

Get the underlying array data.

BehaviorSubject

object BehaviorSubject( value )

Returns a Sling behavior subject. A behavior subject is a value that emits changes to subscribers.

Example behavior subject usage:

let subject = BehaviorSubject(5);
subject.next(subject.getData() + 1);
let value = subject.getData(); // 6

subject.subscribe(function (value) { console.log('Value: ' + value); });

BehaviorSubject Functions

subscribe

void subscribe ( listenerFunction )

Listener function will be automatically called whenever the subject's value changes. Returns the behavior subject.

getHasSubscription

boolean getHasSubscription( functionToCheck )

Check if functionToCheck is in the list of subscribed functions. Returns true if functionToCheck is in the list, false otherwise.

clearSubscription

object clearSubscription( functionToClear )

Remove functionToClear from the list of subscribed functions. Returns the behavior subject.

clearSubscriptions

object clearSubscriptions( )

Remove all subscribed functions. Returns the behavior subject.

next

object next( value )

Set the next value of the subject. All subscribers are automatically called. Returns the behavior subject.

getData

primitive|object getData( )

Get the underlying value.

FormControl

object FormControl( initialValue )

Returns a Sling form control. A form control is a value checked by attached validation functions that emits changes to subscribers.

Example form control usage:

const formControl = FormControl(200);

const validatorFn1 = (val) => {
    if (!isNaN(val) && isFinite(val)) {
        return null;
    } else {
        return { nonNumeric: true };
    }
}

formControl.setValidators([validatorFn1, validatorFn2]);

formControl.getValueChanges().subscribe((value) => {
    console.log(value);
});

const valid = formControl.getValid() === true;
const pristine = formControl.getPristine() === true;
const errors = formControl.getErrors();

formControl.setValue(2);

FormControl Functions

getValue

any getValue ( )

Get the current value of the form control.

getValid

boolean getValid ( )

Get the current validity status of the form control.

getDirty

boolean getDirty ( )

Returns true if the form control value has been changed.

getPristine

boolean getPristine ( )

Returns true if the form control value has not been changed.

getErrors

object[] getErrors ( )

Returns the list of error objects.

getError

object getError ( errorKey )

Return the error object identified by the given key, it it exists. Null otherwise.

getValueChanges

BehaviorSubject getValueChanges ( )

Returns the value changes BehaviorSubject which may be subscribed to on form control value change.

setValue

void setValue ( newValue )

Sets the new value of the form control.

setPristine

void setPristine ( )

Sets the form control as pristine.

setValidators

void setValidators ( validatorFnList )

Sets the validator functions of the form control. Each function accepts a value and returns null if valid or an object with a key.