Plugins

Webix Jet provides predefined plugins and the ability to create custom plugins.

View Plugins

These plugins are enabled for a specific view by view.use():

  • the Menu plugin

  • the UnloadGuard plugin

  • the Status plugin

  • the UrlParam plugin

App Plugins

These plugins are enabled for the whole app with app.use():

  • the User plugin

  • the Theme plugin

  • the Locale plugin

The Menu plugin simplifies your life if you plan to create a menu for navigation:

  • it sets subview URLs for menu options, buttons or other controls you plan to use for navigation;

  • it automatically highlights the right menu option after a page reload or change of the URL.

The plugin must be enabled in the Jet view that contains the menu with view.use() (call it as this.use()). After the plugin name, you must specify the local ID of the Webix control or widget that you want to use as a menu:

// views/top.js
import {JetView, plugins} from "webix-jet";

export default class TopView extends JetView{
    config(){
        return {
            rows:[
                {
                    view:"menu", localId:"menu", data:[
                        { id:"details", value:"Details" },  //show "/top/details"
                        { id:"dash", value:"Dash" }         //show "/top/dash"
                    ]
                },
                { $subview:true }
            ]
        };
    }
    init(){
        this.use(plugins.Menu, "menu");
    }
}

Subview URLs are taken from menu option IDs or from values if there are no IDs. If you want to change some URL, you can add custom subview URLs in the plugin configuration:

// views/top.js
import {JetView} from "webix-jet";
export default class TopView extends JetView {
    config(){
        return {
            rows:[
                {
                    view:"menu", localId:"menu", data:[
                        { id:"details", value:"Details" },  //show "/top/demo/details"
                        { id:"dash", value:"Dash" }         //show "/top/dash"
                    ]
                },
                { $subview:true }
            ]

        };
    }
    init(){
        this.use(plugins.Menu, {
            id:"menu",
            urls:{
                details:"demo/details"
            }
        });
    }
}

Check out the demo >>

Using the Plugin to Change URL Parameters

You can set the Menu plugin in the way that it does not go to a different path, but changes a certain URL parameter. Use the param config setting for this. param accepts a string with the parameter name, e.g.:

// views/top.js
import {JetView} from "webix-jet";
export default class TopView extends JetView {
    config(){
        return {
            rows:[
                {
                    view:"list", localId:"users", data:[
                        { id:"1", value:"Jason Daniels" },  //show "/top?user=1"
                        { id:"2", value:"Kittie Stark" }    //show "/top?user=2"
                    ]
                },
                { /* ...UI */ }
            ]

        };
    }
    init(){
		this.use(plugins.Menu, {
			id:"users",
			param:"user"
		});
	}
}

If you use the Menu plugin with param setting together with the UrlParam, the parameter will be displayed in the URL as a URL segment:

// views/top.js
init(){
	this.use(plugins.UrlParam, ["user"]);
	this.use(plugins.Menu, {
		id:"users",
		param:"user"
	});
}

//show "/top/1"
//show "/top/2"

UnloadGuard Plugin

The UnloadGuard plugin can be used to prevent users from leaving the view on some conditions. For example, this can be useful in the case of forms with unsaved or invalid data. The plugin can intercept the event of leaving the current view and e.g. show a confirmation dialogue.

The syntax for using the plugin is this.use(plugin,handler).

// views/some.js
import {JetView, plugins} from "webix-jet";
...
init(){
    this.use(plugins.UnloadGuard, function(){
        //do something
    });
}

this.use() takes two parameters:

  • the plugin name

  • the function that will define the behavior of the plugin

The UnloadGuard plugin expects a function that returns true if the access must be granted and false if not. The function can also return a promise, resolved and rejected correspondingly.

UnloadGuard can be used for form validation, for example. Let's have a look at a form with one input field that must not be empty:

// views/form.js
import {JetView, plugins} from "webix-jet";

export default class FormView extends JetView {
    config(){
        return {
            view:"form", elements:[
                { view:"text", name:"email", required:true, label:"Email" },
                { view:"button", value:"save", click:() => this.show("details") }
            ]
        };
    }
}

Let's enable UnloadGuard and show a confirmation window if the input is invalid. Webix confirmation windows and other modal boxes return promises.

// views/form.js
...
init(){
    this.use(plugins.UnloadGuard, () => {
		if (this.$$("form").validate())
			return true;
		return webix.confirm({
			title:"Form is incomplete",
			text: "Do you still want to leave?"
		});
    });
}

If the form input is not valid, the function returns a promise. Depending on the answer, the promise either resolves and UnloadGuard lets the user go to the next view, or it rejects. No pasaran.

Check out the demo >>

Status Plugin

This plugin is useful if you want to show the status of data loading in case it takes time, to confirm success or to show an error message.

These are the status messages that you can see:

  • "Ok",

  • "Error",

  • "Connecting...".

Status is enabled with this.use() with two parameters:

  • the plugin name;

  • the plugin configuration (a string or an object).

The plugin configuration must contain at least the ID of the widget that will show the status. This is the simplest way to use the plugin to display the status of data loading into a datatable:

// views/data.js
import {JetView, plugins} from "webix-jet";
import {data} from "models/records";
export default class DataView extends JetView{
    config(){
        return {
            rows:[
                { view:"datatable", autoConfig:true },
                { id:"app:status", view:"label" }
            ]
        };
    }
    init(view){
        view.parse(data);
        this.use(plugins.Status, "app:status");
    }
}

Status configuration can have several properties:

  • target (string) is the ID of the widget where you want to display the status message;

  • ajax (Boolean) enables asynchronous requests;

  • expire (number) defines the time after which the status message disappears (by default, 2000 ms). If you set it to 0, the status message will stay as long as the view is open;

  • data (string) defines the ID of the data component to track;

  • remote (Boolean) enables webix.remote - a protocol that allows the client component to call functions on the server directly.

// views/data.js
...
this.use(plugins.Status, {
    target:"app:status",
    ajax:true,
    expire:5000
});

Check out the demo >>

UrlParam Plugin

The plugin allows treating URL segments as parameters. It makes them accessible via view.getParam() and correctly weeds them out of the URL.

UrlParam is enabled with this.use() with two parameters:

  • the plugin name;

  • an array with parameter name(s).

Let's consider a simple example with a parent view some and its child details:

// views/some.js
import {JetView} from "webix-jet";
export default class SomeView extends JetView{
    config(){
        return {
            rows:[
                { $subview:true }
            ]
        };
    }
}

// views/details.js
const details = { template:"Details" };
export default details;

When loading the URL "/some/23/details", you need to treat the segment after some (23) as a parameter of some. Enable the plugin in the init() method of some:

// views/some.js
import {JetView,plugins} from "webix-jet";
export default class SomeView extends JetView{
   	...
   	init(){
		this.use(plugins.UrlParam, ["id"])
		// now when loading /some/23/details
		const id = this.getParam("id"); //id === 23
   	}
}

details will be rendered inside some, and the fragment 23 will be displayed in the address bar, but will not be resolved as a view module.

Check out the demo >>

User Plugin

The User plugin is useful for apps with authorization.

Login through a Custom Script

This section contains guidelines for using the plugin with a custom script.

Related demos:

Enabling the Plugin

To enable the plugin, call app.use() with two parameters:

  • the plugin name,

  • the plugin configuration.

Plugin Configuration

The plugin configuration can include the following settings:

  • model is a specific session model configured by the developer according to the requirements needed for correct authorization;

  • login (string) is the URL of the login form, "/login" by default;

  • logout (string) is a logout link that could be reserved to automatically perform logout and redirect to afterLogout page (if specified, otherwise the app will use the default redirect to the login page), "/logout" by default;

this.show("/logout"); // from anywhere 

demo

  • afterLogin (string) is the URL shown after logging in, by default taken from app.config.start;

  • afterLogout (string) is a link that works only with logout. Provides specific redirect after logging out via logout link, "/login" by default;

  • user (object) includes some default user credentials;

  • ping (number) is the time interval for checking the current user status, 30000 ms by default;

  • public (function) allows adding pages accessible by unauthorized users.

import session from "models/session";
// ...
app.use(plugins.User, {
    model: session,
    ping: 15000,
    afterLogin: "someStartPage",
    user: { /* ... */ }
});

The Session Model

The session model should contain three main methods:

  • login() logs the user in and also redirects either to the specified afterLogin or App's start page (i.e. default redirect is app.config.start)

The parameters are:

  • user - username;

  • pass - password.

// models/session.js
function login(user, pass){
    return webix.ajax().post("server/login.php", {
        user, pass
    }).then(a => a.json());
}

To implement logging in, you should call the login() method. Once login() is called and the response with some user credentials (JSON object with a name or a promise, for example) is received, the User plugin will automatically redirect the user to the specified start page and will consider the server response as user credentials.

Let's define the do_login() method that will call login() from a form:

// views/login.js
import {JetView} from "webix-jet";
export default class LoginView extends JetView{
    config(){
		return {
			view:"form",
			rows:[
				{ view:"text", name:"login", label:"User Name" },
				{ view:"text", type:"password", name:"pass", label:"Password" },
				{ view:"button", value:"Login", click:() => {
					this.do_login();
				}, hotkey:"enter" }
			],
			rules:{
				login:webix.rules.isNotEmpty,
				pass:webix.rules.isNotEmpty
			}
		};
	}
    do_login(){
        const user = this.app.getService("user");
        const form = this.getRoot();

        if (form.validate()){
            const data = form.getValues();
            user.login(data.login, data.pass).catch(xhr => {
                //error handler
            });
        }
    }
}
  • logout() logs the user out, but does not trigger any automatic redirect until it is initiated by the user or app's logic

// models/session.js
function logout(){
    return webix.ajax().post("server/login.php?logout")
        .then(a => a.json());
}

This is how you can call the method:

const user = this.app.getService("user");
user.logout().then(() => this.show("/login"));
  • status() returns the status of the current user

// models/session.js
function status(){
    return webix.ajax().post("server/login.php?status")
        .then(a => a.json());
}

Each method should return a promise.

Check out basic examples:

Both examples are nearly identical, all requests are implemented with webix.ajax().

E.g. the PHP session model contains requests to php scripts for logging in, getting the current status, and logging out.

Related demo:

Getting User Info & Checking the User Status

The getUser() method returns the data of the currently logged in user.

The getStatus() method returns the current status of the user. It can receive an optional Boolean parameter the server: if it is set to true, the method will send an AJAX request to check the status.

The User plugin checks every 5 minutes (time lapse is defined by the ping setting of the plugin) the current user status and warns a user if the status has been changed. For example, if a user logged in and didn't perform any actions on the page during some time, the plugin will check the status and warn the user if it has been changed.

Adding Public Pages

By default, unauthorized users can see only the "login" page. You can add other pages that all users are allowed to access. Use the public setting of the plugin configuration object. public must be a function that returns true for public pages and false for the rest:

app.use(plugins.User, {
	model: session,
	public: path => path.indexOf("top/data") > -1
});

Login with an external OAuth service (Google, GitHub, etc.)

If you need authorization with an OAuth service, you need to change the way to log in. logout() and status() in the session model are the same as for the custom script.

A demo of login with passport and Google OAuth2

Theme plugin

This is a plugin for changing app themes.

Enabling the Plugin

This is how you can enable the plugin:

// myapp.js
import {JetApp, plugins} from "webix-jet";
...
app.use(plugins.Theme);
app.render();

The plugin has two methods:

  1. getTheme() returns the name of the current theme;

  2. setTheme(name) takes one obligatory parameter - the name of the theme - and sets the theme for the app.

Adding Stylesheets

The service locates links to stylesheets by their title attributes. Here are the stylesheets for the app, e.g.:

<!-- index.html -->
<link rel="stylesheet" title="flat" type="text/css" href="//cdn.webix.com/edge/webix.css">
<link rel="stylesheet" title="compact" type="text/css" href="//cdn.webix.com/edge/skins/compact.css">

Setting Themes

You need to provide a way for users to choose a theme. For example, let's add a segmented button:

// views/settings.js
import {JetView} from "webix-jet";

export default class SettingsView extends JetView {
    config(){
        return {
            type:"space", rows:[
                { template:"Settings", type:"header" },
                { localId:"skin", optionWidth: 120, view:"segmented", label:"Theme",
                    options:[
                        { id:"flat-default", value:"Default" },
                        { id:"flat-shady", value:"Shady" },
                        { id:"compact-default", value:"Compact" }
                    ], click:() => this.toggleTheme() /* not implemented yet, will be a method of this class */
                }
                {}
            ]
        };
    }
}

The option IDs of the segmented have two parts. The first part must be the same as the title attribute of the link to a stylesheet. The theme service gets the theme name from this part of the ID, locates the correct stylesheet and sets the theme.

Let's implement toggling themes as a method of the SettingsView class. The $$ method locates the segmented by its local ID and gets the user's choice. After that, the service sets the chosen theme by adding a corresponding CSS class name to the body of the HTML page.

// views/settings.js
import {JetView} from "webix-jet";

export default class SettingsView extends JetView {
    ...
    toggleTheme(){
        const themes = this.app.getService("theme");
        const value = this.$$("skin").getValue();
        themes.setTheme(value);
    }
}

Restoring The State of the Control

getTheme() can be used to restore the state of the segmented button after the new theme is applied. Let's get the current theme in config() and set the value of the segmented setting it to the correct value:

// views/settings.js
...
config(){
    const theme = this.app.getService("theme").getTheme();
    return {
        ...
        { name:"skin", optionWidth: 120, view:"segmented", label:"Theme", options:[
            {id:"flat-default", value:"Default"},
            {id:"flat-shady", value:"Shady"},
            {id:"compact-default", value:"Compact"}
        ], click:() => this.toggleTheme(), value:theme }
        ...
    };
}

Check out the demo >>

Locale plugin

This is a plugin for localizing apps.

Enabling the Plugin

You can enable the Locale plugin before the app is rendered:

// myapp.js
import {JetApp, plugins} from "webix-jet";
...
app.use(plugins.Locale);
app.render();

You must create files with all the text labels in English and their translations in the locales folder. This is an example of the Spanish locale file:

// locales/es.js
export default {
    "My Profile" : "Mi Perfil",
    "My Account" : "Mi Cuenta",
    "My Calendar" : "Mi Calendario"
};

Changing the Default Locale

The default locale is English. You can change it while enabling the plugin:

// app.js
app.use(plugins.Locale,{ lang:"es" });

Applying the Locale

To translate all the text labels, you need to apply the _() method of the service to each label. The method takes one parameter - a text string. _() looks for the string in a locale file and returns the translation. E.g. this.app.getService\("locale"\).\_\("My Profile"\) will return "Mi Perfil" if Spanish is chosen.

For example, let's apply the _() method to a small menu:

// views/menu.js
import {JetView} from "webix-jet";
export default class MenuView extends JetView {
    config(){
        const _ = this.app.getService("locale")._;

        return {
            view:"menu", data:[
                { id:"profile", value:"My Profile" },
                { id:"account", value:"My Account" },
                { id:"calendar", value:"My Calendar" }
            ],
            template:(obj)=>{
                return _(obj.value)};
            }
        };
    }
}

Dynamically Changing Locales

If you want your app to be multilingual and let the users to choose a language, you can add a control for that.

To set a new language, use the setLang() method of the locale service. setLang() takes one parameter - the name of the locale file. When a user chooses a language, a locale file is located and the app language is changed.

Do not call setLang() in lifetime handlers of JetView! It calls app.refresh() that re-renders all the views, which will start an infinite loop.

Let's create a simple page for app settings with a segmented button that will be used to choose languages. Note that the IDs of the button options should be the same as the locale file names (e.g. "es", "en").

// views/settings.js
import {JetView} from "webix-jet";

export default class SettingsView extends JetView {
    config(){
        return {
            type:"space", rows:[
                { template:"Settings", type:"header" },
                { localId:"lang", optionWidth: 120, view:"segmented", label:"Language", options:[
                    { id:"en", value:"English" },
                    { id:"es", value:"Spanish" }
                ], click:() => this.toggleLanguage() }, //will be implemented as a method of this class
                {}
            ]
        };
    }
}

toggleLanguage() is a class method that will:

  • get the value of the segmented button,

  • pass it to setLang() that will set the locale.

// views/settings.js
import {JetView} from "webix-jet";
export default class SettingsView extends JetView {
    ...
    toggleLanguage(){
        const langs = this.app.getService("locale");
        const value = this.$$("lang").getValue();
        langs.setLang(value);
    }
}

Getting the Current Locale

To check the current language, use the getLang() method. It can be useful for restoring the value of the settings control, e.g.:

// views/settings.js
import {JetView} from "webix-jet";
export default class SettingsView extends JetView {
    config(){
        const lang = this.app.getService("locale").getLang();

        return {
            ...
            { name:"lang", view:"segmented", label:_("Language"), options:[
                {id:"en", value:"English"},
                {id:"es", value:"Spanish"}
            ], click:() => this.toggleLanguage(), value:lang }
            ...
        };
    }
    ...
}

Check out the demo >>

Storing the Language

To restore the last chosen language when the app is opened again, set the storage setting of the plugin, e.g. you can choose the local storage:

// app.js
...
app.use(plugins.Locale, { storage:webix.storage.local });

Path for the Locale Plugin

You can also set path to a subfolder (subpath) inside the jet-locale folder, in which you want the plugin to look for translations:

// app.js
...
app.use(plugins.Locale, { path:"some" });

You can also disable locale loading from jet-locales using the same setting:

app.use(plugins.Locale, { path:false });

Combining with Webix Locales

The Locale plugin has the additional setting for using Webix locales alongside with the Jet app locale.

app.use(plugins.Locale, {
	webix:{
		en:"en-US",
		// ...other locales
	}
});
// calls webix.i18n.setLocale("en-US")

Splitting Localization

If a lot of text in your app needs to be translated, you can split localizations and load them when they are needed. The source of a locale can be anything, e.g. inline or a result of an AJAX request:

webix.ajax("/server/en/forms").then(data => {
    data = data.json();
    this.app.getService("locale").setLangData("en", data);
});

Configuring Polyglot

The Locale plugin uses the Polyglot library, which has settings of its own. You can use these settings in the config of the plugin:

this.use(plugins.Locale, { polyglot:{ /* ...Polyglot settings */ } });

Custom Plugins

You can create a service and use it as an app-level plugin.

//helpers/state.js
export function State(app){
    const service = {
        getState(){
            return this.state;
        },
        setState(state){
            this.state = state;
        },
        state:0
    };
    app.setService("state", service);
}

Such services should be included into the application in the start file (e.g. myapp.js):

//myapp.js
import {state} from "helpers/state.js";

const app = new JetApp({...}).render();
app.use(state);

And any view will be able to use it as:

{ view:"button", value:"Add new",  click:() => {
    if (this.app.getService("state").getState())
        //do something
}}

Last updated