New Strategy of Complex Widgets: Why Webix Jet

This year we have worked out a new concept of complex widgets and have already released the new ones: File Manager, Document Manager, Query, and User Manager. They were built from scratch as fully configurable Webix Jet app modules, which makes them flexible, powerful… and yet not very easy to master regarding the code. However once you grasp the concept, it opens great opportunities.

Life after File Manager 7.2: The benefits of Jet-based widgets

Let’s explore the technical features of the new widgets and learn of all the benefits of the Jet-based approach.

Why We Did it in the First Place

Webix complex widgets are basically small applications. While they can cope with their primary task well, it is not easy to customize them for specific purposes, as their public API does not allow to get to the inside logic too much. But people really need this flexibility.

That is why we came up with the idea to use Webix Jet to create complex widgets as mini apps. This solution keeps complex widgets well-structured and very flexible for customization and scalable for new functionality.

What’s New for End-Users

First of all, we have not simply rewritten the code, we have added a lot of new features to the end users. For example, File Manager 7.2 was enhanced with:

  • Three-mode interface: list, cards and double view
  • Preview of files
  • Text editor
  • Adding files and folders
  • Downloading and uploading files
  • Adaptive behavior to serve small screens on mobile devices

Webix File Manager

Live demo >>

Document Manager went even further and added a number of features outside the scope of simple file-system tasks:

  • Trash for storing deleted files and folders;
  • Marking files as favorite;
  • Sharing files with other users;
  • The ability to open recently viewed files;
  • Adding tags to files and folders;
  • Search by names and tags;
  • Commenting files;
  • Viewing and editing Excel files.

Webix DocManager

Live demo >>

Query emerged with a user-friendly design and convenient UX for filtering.

Webix Query

Live demo >>

User Manager – did you hear that? – is an absolutely new widget for managing users and their access rights:

  • managing user access rights
  • uniting access rights into roles
  • permissions and actions audit

user manager

Live demo >>

This is what end-users will get from the Jet-based widgets out of the box. Anything that’s outside the scope can be easily added by you, developers.

What’s New for Developers

To the experienced Webix users, the Jet-based widgets can seem a bit unorthodox. They are built differently and require a different approach from a developer. While each widget has its own specific features, they all have these things in common:

  • Webix Jet-based structure and OOP
  • Reactive properties instead of conventional configuration settings and event system
  • Customizable view classes and services instead of classic Webix API
  • Overrides as a means of customization

Webix Jet and UI Customizations

Webix Jet takes care of neat code organization and the basic architecture of complex widgets. Its major features are:

  • keeping the UI in separate modules (views) and combining them to create the needed interface
  • keeping the app logic in special modules (services)
  • nothing is private and everything can be reached and modified by code from outside a widget

With classic OOP inheritance, you can:

  • extend views and services with new functionality
  • override the current functionality
  • add new features and components

For instance, to modify the UI, find the necessary view class and override its config method so that it returns a different Webix config. Let’s add a new option to the action menu of the Query widget:

class ActionsPopupView extends query.views.actions {
    config() {
        const ui = super.config();

        const list = ui.body;
        list.data.push({ id: "custom", value: "Custom option" });
        list.yCount = 5;

        return ui;
    }
}

Live demo >>

To change the way specific components of a widget work, you need to override init and other view class methods. For example, to add an action for the above menu option, add a handler for it:

class ActionsPopupView extends query.views.actions {
    config() {
        ...
    }
   
    init(){
        super.init();
       
        this.on(this.app, "action", (id, item) => {
            if (id === "log") this.LogFilter(item);
        });
    }
   
    LogFilter(item){
        console.log(`Filter ID: ${item}`);
    }
}

Live demo >>

Reactive State: How to change properties and watch changes

Alongside with the usual configuration settings like width, url, etc, Jet-based widgets have the reactive properties enabled by Jet reactive state. This technique was created to enable straightforward communication between all the widget parts. State properties can reflect the value of any parameter as well as notify about its change, so that any listener can react to it if necessary.

Compared to events and parameters, a reactive state:

  • allows to avoid cumbersome current value checks for config parameters
  • does not clutter the event bus, which happens sooner or later if you rely on app-wide events too much
  • a listener does not have to exist at the moment a parameter changes; it can react to the change once it is created

You can define reactive properties right in the widget configuration:

// the folder with the recently viewed files is initially open
view:"docmanager", id:"dm", source:"recent"

The reactive state of a widget is available via its getState() method:

const state = $$("dm").getState();

After that you can:

  • subscribe to a parameter and run your custom logic upon changes at any moment
{
    view: "docmanager",
    url: "https://docs.webix.com/docmanager-backend/",
    on: {
        onInit: app => {
            const state = app.getState();
            state.$observe("source", (v,o) => {
                /* custom logic here */
            });
        },
    }
}
  • change parameters, and the widget will react to the change
$$("dm").getState().source = "trash";

You can change several state properties at once:

const state = $$("docManager").getState();
state.$batch({
    source: "files",
    path: "/Music",
    mode: "cards"
});

You don’t need to refresh the widget afterwards. All listeners of the reactive state will do it on their own.

Live demo >>

Overrides and Services: Customizing widget logic

Jet-based widgets do not have the usual public API, that’s why you won’t be able to customize them in the old way. Instead, widgets have service modules with the methods for the inner logic. You can redefine any service, e.g. the Backend service, if you require another logic of server-client communication.

Any method can be called directly, e.g.:

// get all files and folders from the root of the file system
$$("filemanager").getService("backend").files("/").then(files => {
    ...
});

…and any method can be redefined. For example, you can redefine files to load the contents of directories in a different way:

class MyBackend extends fileManager.services.Backend {
    // you can use calls to real backend server
    files(id) {
        return webix.ajax("//docs.webix.com/filemanager-backend/files", { id })
            .then((data) => data.json());
    }

    // ... other methods
}

webix.ui({
    view: "filemanager",
    url: "https://docs.webix.com/filemanager-backend/",
    override: new Map([[fileManager.services.Backend, MyBackend]]),
});

Live demo >>

Common Customizing Guidelines

To know what exactly to override to get the result, you need to read the source code. However, there are basic guidelines on what to look for:

  • changing the UI: look for a view class that contains the component (you can also consult class maps in the docs, e.g.) and redefine the config method of the view class: example
  • changing the way the UI works: look for a view class that contains the component and redefine the init method or other class methods: example
  • changing or adding functionality: add or redefine methods of view classes and services; e.g. Operations would be the service you want to look for if you want to handle app actions in a different way: example
  • adapting the widget to your custom backend code: look for the Backend service or other services that deal with backend (e.g. Tags, Users), their local data counterparts (e.g. LocalData, LocalUsers, LocalTags) and override their methods: example

Nothing is properly private in Jet-based widgets. You can get to any method, property or component and change them. This is both a blessing and a great responsibility.

Flexibility is Responsibility

Flexibility means that you do not have to invent long and convoluted ways to change components and behavior. Everything is open to customization and improvement.

Flexibility also means responsibility …and the love for reading the source code. Since there is not much of the conventional API, we did not document every single method of any view or service – except for the Backend service that is the first candidate for customization 🙂 We also provide general configuration guides and ‘How to’ pages with the ideas for the common tasks.

We are of course updating the documentation when our users find it insufficient. However, to truly understand how Jet-based widgets work, what the views and services are, etc, you will need to read the source code. If you have any problems, feel free to contact us. We are always happy to help you.

What’s Next

Meanwhile, we are already working on several Jet-based solutions to extend the collection of Webix complex widgets. If you are waiting for a specific tool, please share your ideas in the comments section.

Stay tuned for the updates and stay safe 🙂