View Communication

Views are separated, and there should be some means of communication between them.

URL Parameters

Views can communicate through URL parameters that are added after segments for the related views. You can pass parameters with the URL in the show method. One of the typical ways to use URL parameters is loading data into views as they are initialized (e.g. list-form pattern).

Setting Parameters of Self

Use this.setParam to set the URL parameters of the current view:

this.setParam("mode", "12", true); // some?mode=12

For example, let's change the mode parameter when a segment of Segmented is clicked:

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

export default class TopView extends JetView {
    config(){
        return {
            rows:[
                { view:"segmented", options:["full", "brief"], on:{
                    onChange: function(){
                        this.$scope.setParam("mode", this.getValue(), true);
                    }
                }}
            ]
        };
    }
}

Check out the demo >>

Work with URL parameters can be simplified with the UrlParam plugin.

Setting Parameters of Subviews

To set URL parameters for a view, pass them in view.show after the URL segment. In the example below, show will be called every time a record is selected in the table:

// views/data.js
import {JetView} from "webix-jet";
import {getData} from "models/alldata";

export default class DataView extends JetView {
    config(){
        return {
            cols:[
                { view:"datatable", select:true, localId:"data", autoConfig:true },
                { $subview:true }
            ]
        };
    }
    init(){
        const table = this.$$("data");
        table.parse(getData());

        this.on(table, "onAfterSelect", id =>
            this.show(`form?id=${id}`)
        );

        table.select(table.getFirstId());
    }
}

form here is a Jet view file with a form, e.g.:

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

export default class FormView extends JetView {
    config(){
        return {
			view:"form", elements:[
				{ view:"text", name:"name" },
				{ view:"text", name:"email" }
			]
		};
    }
}

Getting Parameters

To get the id parameter in the form, call the getParam method. urlChange is the best lifetime handler for this since init is called only once during the lifetime of a view.

// views/form.js
import {JetView} from "webix-jet";
import {getData} from "models/records";

export default class FormView extends JetView{
    config(){
        return {
            view:"form", elements:[
                { view:"text", name:"name" },
                { view:"text", name:"email" }
            ]
        };
    }
    urlChange(view){
        const id = this.getParam("id");
        if (id)
            view.setValues(getData(id));
    }
}

In this simple example, when a table row is selected, a form is filled with the related data. getData is a function that returns data.

Live demo >>

More on GitHub

Getting Parameters: Last Resort

getParam can get URL parameters of the current view and its direct parent. If you really need to access parameters of subviews, you can use the url parameter of lifetime handlers. However, keep in mind that accessing parameters of other views is risky, because you must be sure of the current app structure.

// views/form.js
import {JetView} from "webix-jet";
import {getData} from "models/records";

export default class DataView extends JetView {
    config(){
        return {
            cols:[
                {
                    view:"datatable",
                    select:true,
                    localId:"data",
                    autoConfig:true
                },
                { $subview:true }
            ]
        };
    }
    urlChange(view, url){
        const subId = url[1].params.id;
    }
}

Several URL Parameters

You can also pass several parameters to view.show:

this.show("form?name=Jack&email=some");

Events

You can use the in-app event bus for view communication.

Calling an Event

To call/trigger an event, call app.callEvent. You can call the method by referencing the app with this.app from an arrow function [1]:

// views/subview.js
import { JetView } from "webix-jet";
import { some_data } from "models/somedata";
export default class SubView extends JetView {
	config(){
		return {
			view:"datatable",
            select:true,
            autoConfig:true,
			on:{
				onAfterSelect:row =>
                    this.app.callEvent("grid:record:select", [row])
			}
		}
	}
    init(view){
        view.parse(some_data);
    }
}

Calling events is necessary mostly for custom events. Inner Jet events and Webix events are called automatically (e.g. the onItemClick event of List).

Attaching an Event Listener

You can attach an event handler to the event bus in one view and trigger the event in another view.

A listener can be attached with this.on. The benefit of this way is that the event handler is automatically detached when the view is destroyed. this.on can handle app and Webix events.

// views/parentview.js
import { JetView } from "webix-jet";
import { some_data } from "models/somedata";
export default class ParentView extends JetView {
	config(){
		return {
			rows:[
				{ view:"toolbar", elements:[ /* some toolbar controls*/ ] },
				{
					cols:[
						{
                            view:"list",
                            localId:"list",
                            template:"#name#",
                            data:some_data
                        },
						{ $subview:true } // here lives the datatable
					]
				}
			]
		}
	}
	init(){
		this.on(this.app, "grid:record:select", row => {
			this.$$("list").select(row.id);
		});
	}
}

Services

A service is a way to share functionality. You can create a service connected to one of the views, and other views can communicate with the view through the service.

With ES6, the problem can also be solved with creating and requiring a module. If you create two apps that require the same module, there is only one instance of the shared code. Services are better, because a new instance of a service is created for each new instance of a Jet view class.

Initializing Services

To set a service, call the app.setService method. setService requires two parameters:

  • service - (string) the name of the service

  • obj - (object) all methods that you want to call from other Jet views

For example, let's create a masterTree view and let other views get the ID of a selected item in the master tree.

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

export default class treeView extends JetView{
    config(){
        return { view:"tree" };
    }
    init(view) {
        this.app.setService("masterTree", {
            getSelected : () => view.getSelectedId();
        });
    }
}

Using Services

To get to the service and its methods, use the app.getService method. getService requires one parameter - the name of the service.

This is how you can get the ID of the selected node in the master tree and use it to fill a form with correct values:

// views/form.js
import {JetView} from "webix-jet";
import {getData} from "../models/records";

export default class FormView extends JetView{
    config(){
        return {
            view:"form", elements:[
                { view:"text", name:"name" },
                { view:"text", name:"email" }
            ]
        };
    }
    init(view){
        const id = this.app.getService("masterTree").getSelected();
        view.setValues(getData(id));
    }
}

Apart from view communication, services can be used for loading and saving data. You can read about it in the "Models" chapter.

You can also use services to create app-level plugins.

//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) and enabled with app.use:

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

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

Any view will be able to use the service as:

{ view:"button", value:"Add new",  click:() => {
    if(!this.app.getService("state").getState())
        this._jetPopup.showWindow();
}}

Which Way of View Communication To Choose?

Each of these ways is good for different cases of view communication. Let's summarize the typical uses of each one.

1. It is better to use URL parameters to provide the information relevant for loading of the initial data into subview modules or for the initial state of Webix views.

2. Events are necessary for reacting to actions by multiple receivers. A typical case is communication between subviews and parents, when a subview throws an event and the parent handles it.

3. Services are relevant for storing a common state that can be used throughout the application or storing common methods that should be accessed by any Jet view. Communication with services works for all views and subviews regardless of their relations (whether they are a subview and a parent or not) and their life-cycles (whether views exist in the app at the same moment or not).

Also remember that you can't use the same service for two instances of a view class, e.g. if you create a file manager with two identical file views. For each instance of the class a new service is created. Use services if other ways aren't possible.

View Class Methods

One more effective way of connecting views is methods. Unlike events, methods can also return something useful. In one of the views, you can define a method, and in another view, you can call this method.

Methods are preferable when you want to connect views with their subviews. Methods can only be used for view communication when you know that a view with the necessary method exists. A method is declared in the child view and called in its parent.

Events vs Methods

Have a look at the example. Here is a view that has the setMode method:

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

export default class ChildView extends JetView{
    config(){
        return {
            { view:"spreadsheet" }
        }
    }
    setMode(mode){
        this.app.config.mode = mode;
    }
}

And this is a parent view that will include ChildView and call its method:

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

export default class ParentView extends JetView{
    config() {
        return {
            rows:[
                {
                    view:"button", value:"Set mode",
                    click:() => {
                        this.getSubView().setMode("readonly");
                    }
                },
                { $subview:"child" }    //load "views/child"
            ]
        };
    }
}

this.getSubView refers to ChildView and calls the method. getSubView can take a parameter with the name of a subview if there are several subviews, as you will see in the "Referencing Views" chapter.

You can use methods for view communication in similar use-cases, but still events are more advisable. Now let's have a look at the example where methods are better then events.

Methods vs Events

Suppose you want to create a file manager resembling Total Commander. The parent view will have two file subviews, each of them contains a list of files:

// views/manager.js
...
config() {
    return {
        cols:[
            { name:"left", $subview:"fileview" },     // "views/files"
            { name:"right", $subview:"fileview" }    // "views/files"
        ]
}}

Here each subview has a name. fileview has the loadFiles method. Let's tell the file manager, which paths to open in each file view:

// views/manager.js
...
init() {
    this.getSubView("left").loadFiles("a");
    this.getSubView("right").loadFiles("b");
}

Both subviews are referenced with this.getSubView.

Footnotes

[1]:

To read more about how to reference apps and view classes, go to "Referencing views".

Last updated