Вы находитесь на странице: 1из 32

Table

of Contents
1. Introduction 1.1
2. Basics 1.2
3. Diving Deeper 1.3
4. Advanced Techniques 1.4
5. And Some More 1.5

2
Introduction

Introduction
Introduction to Webix Jet Guide
This guide provides all the information needed to start creating web applications with Webix Jet. It
dwells on the process of creating an application, from making separate modules and loading them
with data, to integrating them by establishing connections and organizing navigation.

Updates History
Latest update made on June 27, 2016

3
Basics

Basics
Webix is a library of UI components and you don’t need any special techniques to create apps with it.
However, while more and more components are added to a project there’s a risk to get a mess of code.
This guide will provide you with an easy and convenient way of creating apps with Webix by means
of using Webix Jet framework.

Advantages of Webix Jet


Webix Jet allows you to create a flexible, easy maintainable app, where data and visual presentations
are clearly separated, interface elements can be easily combined and reused, all parts can be
developed and tested separately - all with minimal code footprint. It has a ready to use solution for all
kinds of tasks, from simple admin pages to fully-fledged apps with multiple locales, customizable
skins and user access levels.

Webix Jet is a fully client-side solution, and can be used with any REST-based data api. So there
aren’t any special requirements to the server.

Getting started
To begin with, you should grab the app package from http://webix.com/packages/webix-app-start.zip
and unpack it to the root folder of your webhost.

Run the index.html file and you’ll see the app’s interface. Let’s have a look what it has inside.

The app’s structure


The codebase of our app consists of:

index.html file that is a start page and the only html file in our app
app.js file that includes the configuration of the app;
the views folder containing modules for interface elements;
the models folder that includes modules for data operations;
the libs folder where all the applied libraries are located, including the Webix library.

How it works
The basic principle of creating an app is the following. The app is a single page. We divide it into
multiple views which will be kept in separate files. Thus, the process of controlling the app’s behavior
gets much easier and quicker.

In order to navigate between pages we will change the url of the page. But as we are writing a single
page app, we will change not the main url, but only a part after hashbang. The framework will react to
the url change and rebuild the interface from these elements.

The app splits the url into parts, finds the corresponding files in the views folder and creates an
interface by combining UI from those files.

For example, there are next 2 files in the views folder of our app:

4
Basics

top.js
start.js

If you set the path to “index.html#!/top/start”, the interface described in the views/top.js file will be
rendered first. Then the interface from views/start will be added in some cell of top level interface:

index.html#!/top/start

Defining a view module


views/start

The start.js file describes a start page view

//views/start.js
define([],function(){
return {
template:"Start page"
};
});

This is a module that returns a template with the text of the page. Each module starts from the
“define” call which contains a description of ui inside.

You can look at this page by opening the url index.html#!/start

views/top

The views/top module defines the top level view, that contains a menu and includes the start page
view, which we have described above:

//views/top.js
define([
5
Basics

"views/start"
], function(start){
return {
cols:[
{ view:"menu" },
start
]
};
});

In the above code we have described a layout with two columns. At the top of the file we are
providing the list of dependencies, which we will use in this layout.

Open the path index.html#!/top and you’ll see the page with start view inside of the top one.

You can have a look at the demo that shows how a basic application with Webix Jet is created.

Creating subviews
As it’s already been said, our app consists of a single page. How is the process of views manipulation
organized?

Let’s check the following code:

//views/top.js
define([], function(){
return {
cols:[
{ view:"menu" },
{ $subview: true }
]
};
});

The line { $subview: true } implies that we can enclose other modules inside of the top module. The
next segment of the url will be loaded into this structure. So for rendering the interface including a
particular subview, put its name after index.html#!/top/ like index.html#!/top/start. The { $subview:
true } placeholder will be replaced with the content of a subview file ( views/start.js in the above
example ) and the corresponding interface will be rendered.

For example, we've got a data.js view which contains a datatable. If you enter the url
index.html#!/top/data, you’ll get the interface with a menu in the left part and a datatable in the right
part:

index.html#!/top/data

6
Basics

Then, add one more /top subdirectory into the path. The url will look as index.html#!/top/top/data and
the app will have another menu view inserted into the first one. This view will contain the datatable:

index.html#!/top/top/data

The described way of inserting subviews into the main view is an alternative to specifying the
necessary subview directly in the main view code.

A demo that illustrates the process of adding a subview into an application is given here.

Loading data with models


7
Basics

While views contain the code of interfaces, models are used to control the data. We’ll consider data
loading on the example of the views/data.js file. It takes data from the models/records module.To load
data into the view we’ll use data collection. Take a look at the content of the records.js file:

//models/records.js
define([],function(){
var collection = new webix.DataCollection({
url:"data.php"
});

return {
data: collection
};
});

In this module, we create a new data collection that loads data from the data.php file. The module
returns a helper method that provides access to our DataCollection.

The views/data module has the following code:

//views/data.js
define(["models/records"],function(records){
var ui = {
view:"datatable", autoConfig:true
};

return {
$ui: ui,
$oninit:function(view){
view.parse(records.data);
}
};
});

As you can see, this module returns an object that differs from those we described earlier. There are
two variants of the return object. It can be simply a description of interface or an extended object with
the following properties:

the $ui property defines the interface of the component that will be initialized. In our example
it’s datatable;
the $oninit function specifies that data from the records model will be loaded into the view after
its creation.

You can check the demo describing the usage of a model with data in an application.

8
Diving Deeper

Diving Deeper
In the previous part of the guide we’ve described the client side part of the app and basics of building
interface from modules. Now we will tell you about data saving on the server side and a more
extended usage of views.

Loading and saving data on the server side


All data related logic should be stored in separate modules - models. A model is created for each
particular data category (users, books, etc.). It guarantees code safety of the whole app in case you
need to change something in a single model.

In order to load and save data with the help of REST API, we should initialize a data collection in the
following way:

// models/records.js

define([],function(){

var collection = new webix.DataCollection({


url:"rest->/records.php",
save:"rest->/records.php"
});
return {
data: collection
};
});

where:

url:"rest->/records.php" defines the URL of the data loading script


save:"rest->/records.php" defines the URL of the data saving script

Those parameters allow applying changes made on the client side to the database on the server

The described functionality is presented in the following demo.

View Handlers
A view module returns an object which can have a set of special properties:

define([], function(){
return {
$ui:{ /*ui config*/},
$oninit:function(view, $scope){ /*after creating*/},
$onurlchange:function(config, url, $scope){ /*after navigation*/
$ondestroy:function(){ /*before destroy*/}
}
});

Let’s consider each property in detail.


9
Diving Deeper

$ui

The ui property gives the description of Webix interface. It can include any possible content of a
component (settings, events, data, templates).

Our interface can also include other views. Such a structure is called composition:

// views/top.js

define(["app"], function(app){
var ui = {
cols:[
{ view:"menu", id:"top:menu"},
{ $subview:true}
]
};

return {
$ui: ui
};
});

$oninit

This method is called each time when a view is created. It’s used to load data into components and to
set event handlers. The handler gets two parameters: the object of a newly created view and the
current scope.

// views/data.js

define(["models/records"],function(records){
return {
$ui: {
view:"datatable"
},
$oninit:function(view,$scope){
view.parse(records.data);
}
}
});

The important note: when the url is changed, the oninit method won’t be called for existing views. For
example, if we go from /top/data to /top/start, $oninit will be called for the "start" only. As the top
view has already been rendered, $oninit won’t be called for it.

$onurlchange

The method is called when the "local" url is changed (after going to the next page). It’s used to restore
the view’s state by means of the url’s parameters.

// views/top.js

define([
"app"
],function(app){
10
Diving Deeper

var ui = {
cols:[
{ view:"menu", id:"top:menu"},
{ $subview:true}
]
};
return {
$ui: ui,
$onurlchange:function(config, url, $scope){
$$("top:menu").select(url[0].page);
}
}
});

where:

config - parameters of the url


url - contains the names of segments and all parameters of the url
$scope - the current scope

$ondestroy

The method is called each time when a view is destroyed. It can be used to destroy temporary objects
(such as popups and event handlers) and prevent memory leaks.

// views/data.js

define([
"models/records"
], function(records){
var popup, eventid;

return {
$ui: {
view:"datatable", editable:true
},
$oninit:function(view,$scope){
popup = webix.ui({
view:"popup",
body:"Data is updated"
});
eventid = records.data.attachEvent("onDateUpdate", function
popup.show();
});
view.parse(records.data);
},
$ondestroy:function(){
popup.destructor();
records.detachEvent(eventid);
}
}
});

Notice that the change of the url won’t call $ondestroy for the views that present in the new url. For
11
Diving Deeper

example, if we go from the page /top/data to the page /top/start, the $ondestroy method will be called
for the data view only.

The main use case of the ondestroy handler is removing of any unnecessary UI or unused event
handlers. There are some other, simpler ways to handle this use-case though.It’s more handy to use
scope for both initializing and destructing popups and event handlers.

You can check the demo showing how handlers are used in an application.

What is scope?
As you can see, the $oninit and $onurlchange properties contain functions that take the $scope
parameter as an argument. So, we need to explain the term scope.

Scope is an object that keeps the state of a part of an interface. Scope is automatically created for the
whole application. What is more, scope is created for each subview which is presented in the code.

For example, for an app which shows the path /top/data we will have 2 scopes. One for top view and
one for data view.

Using scope
Scope helps to create temporary elements, e.g. popups which are automatically destroyed together
with the current view. It also allows making event handlers which are detached when the current view
is no longer needed and destroyed.

For example:

define([
"models/records"
],function(records){
var ui = {
view:"datatable"
};

return {
$ui :ui,
$oninit:function(view){
var popup = ({
view:"popup",
body:"Data is updated"
});
records.data.attachEvent("onDataUpdate", function(){
popup.show();
});
view.parse(records.data);
}
}
});

The above code defines that after updating a record in datatable a popup "Data is updated" appears.
This message will try to appear, even when another view will be loaded instead of the datatable, as
event handler never detaches by itself. To solve this issue, we can use the $scope.on method:

12
Diving Deeper

$oninit:function(view, $scope){
$scope.on(records.data, "onDataUpdate", function(){
popup.show();
});
}

Due to attaching event by the $scope.on method, the handler of the onDateUpdate event will be
detached when a new view will be loaded on the page.

In order to create a popup that will be destroyed together with the current view, the $scope.ui()
method can be used instead of webix.ui(). Thus, the code below

define([
"models/records"
],function(records){
var popup;
return {
$ui: {
view:"datatable"
},
$oninit:function(view){
popup = webix.ui({ view:"popup"});
},
$ondestroy:function(){
popup.destructor();
}
}
});

can be redefined as:

$oninit:function(view,$scope){
var popup = $scope.ui({ view:"popup"});
});
//no need to define $ondestroy

To better understand how scope helps to make implementation of an application handier, check the
demo.

In-app navigation
Navigation between elements of an app is implemented by means of changing the url of the page.

1) direct url navigation ( A tag or document.location )

<a href="#!/top/data">Data</a>

Direct url can be specified within Webix menu as value of href parameter:

// views/top.js

var menu = {
view:"menu",
data:[
13
Diving Deeper

{ value:"DashBoard", href:"#!/top/start" },
{ value:"Data", href:"#!/top/data" }
]
};

2) the app.show method is applied to the whole application:

// views/top.js

define([
"app"
],function(app){
var ui = {
view:"menu",
select:true,
data:[
{ id:"start", value:"Dashboard"},
{ id:"data", value:"Data"}
],
on:{
onAfterSelect:function(id){
app.show("/top/"+id);
}
}
};
return {
$ui: ui
};
});

In the above code it’s stated that on clicking an item in the menu, we’ll get its id in the url of the page,
e.g.: #!/top/data. Each time when we select some other item, the whole app will be rerendered and the
url will change.

3) the $scope.show method

This way is more convenient, as the method allows reloading just the part of the app. There are two
variants of loading data with the scope.show method:

loading a subview of the same level (sibling):

// views/start.js

this.$scope.show("data");

The current subview will be changed to "data". For example: the path index.html#!/top/start will be
reloaded as #!/top/data

loading a child subview

//views/start.js

this.$scope.show("./news");

In this case the news subview will be loaded inside of the "start" view and the path #!/top/start will be
14
Diving Deeper

changed into #!/top/start/news.

Check the demo to see how the described ways of navigation are used in an application.

Working with menus


It is quite common to have some kind of menu and a subview next to it. When item in menu selected
we can navigate the subview to the selected page:

{
view:"menu", id:"top:menu",
select:true,
data:[
{ value:"DashBoard", id:"start", href:"#!/top/start" },
{ value:"Data", id:"data", href:"#!/top/data" }
]
}

The above code is enough, but we have a problem. When we are entering some url manually in the
address bar, the correct page will be loaded, but the related item in the list won't be selected
automatically.

We can use the onUrlChange handler:

return {
$ui:ui,
$onurlchange:function(config, url, $scope){
$$("top:menu").select(url[0].page);
}
}

or we can use the menu plugin.

A plugin is an independent module used to extend the app’s functionality.

The menu plugin can be used for highlighting a menu item or an element when a page is changed. To
enable menu, you need to include the plugin in the app.js file in the following way:

define(
"libs/webix-mvc-core/plugins/menu"
],function(menu){
// after the description of the app configuration
app.use(menu);
});

The plugin is used in a view by means of the $menu parameter which takes the id of the menu view as
its value. The id of the menu view is specified in the description of the app’s interface.

return {
$ui:ui,
$menu:"top:menu"
}

To see how the menu plugin works in an application, check the corresponding demo.

15
Diving Deeper

16
Advanced Techniques

Advanced Techniques
Creating popups with $windows
In the previous part we’ve described a popup creation by means of the scope. It's also possible to
initialize a popup or a window with the help of the $windows parameter. It allows creating several
windows at once when the current view is shown and destruct them when the current view is
destroyed.

In the example below the code creates a list view and a few popups.

define([
"models/records"
],function(records){
return {
$ui: {
view:"datatable", on:{
onItemClick:function(){
$$("win2").show();
}
}
},
$windows:[
{ view:"window", id:"win1" },
{ view:"popup", id:"win2" }
],
$oninit:function(view,$scope){
view.parse(records.data);
$$("win1").show();
},
}
});

When a new view will be loaded on the page, all the popups and windows will be destroyed together
with the list view.

The demo illustrating the creation of several windows is available here.

Creating connections between views


Since our app is split into several views, each of them is located in a separate file and "doesn't know"
about all the others. On the one hand, it's made for the safety's sake: we can't break the whole app by
spoiling the code of one view. On the other hand, views should be connected in some way in order to
communicate. There are several methods to do it.

Triggering events

Communication between views can be implemented through the global event bus. You can attach an
event handler to the global event bus in one view and trigger the event in another view. For example,
in one view we will have the following code:

17
Advanced Techniques

//views/data.js

define([
"app",
"models/records"
],function(app, records){
var ui = {
view:"datatable",
visibleBatch:"info",
columns:[
{ id:"title", header:"Title", fillspace:true },
{ id:"year", header:"Year", batch:"info" },
{ id:"votes", header:"Votes", batch:"stats" },
{ id:"rating", header:"Rating", batch:"stats", hidden:true
{ id:"rank", header:"Rank", batch:"stats", hidden:true }
]
};

return {
$ui: ui,
$oninit:function(view, $scope){
view.parse(records.data);

$scope.on(app, "detailsModeChanged", function(mode){


view.showColumnBatch(mode);
});
}
};
});

The view has an event handler "detailsModeChanged" which shows a particular column group when
the event is triggered.

In another view a segmented button is initialized. A click on the segmented button triggers the call of
the "detailsModeChanged" event defined in the above view:

// views/top.js

define([
"app"
],function(app){

var mode = {
view:"segmented", options:[
{id:"info", value:"Info"},
{id:"stats", value:"Stats"}
],
on:{
onChange:function(newv){
app.callEvent("detailsModeChanged", [newv]);
}
}
}

18
Advanced Techniques

...
});

Thus, on clicking the segmented button the detailsModeChanged event will fire and the
corresponding column group will be rendered in the datatable.

You can check the demo that shows how views can be connected with the help of events.

Note that you can also attach event handler to app, but in this case they should be manually detached
each time the view is destroyed:

Event handler shortcuts ( or aliases )

Handlers of the events which are used for connecting views can be presented in a more compact way.

For example, app.attachEvent

app.attachEvent("eventName", function(){});

can be replaced with app.on

app.on ("eventName", function(){});

Instead of the callEvent method

app.callEvent("eventName", [ ... ]);

we can use the app.trigger one

app.trigger("eventName", [ ... ]);

There's also the app.action() method that unites both the click handler and the callEvent method. For
example, let's consider the code of initialization of a segmented button:

// views/top.js

var mode = {
view:"segmented", options:[
{id:"info", value:"Info"},
{id:"stats", value:"Stats"}
],
on:{
onChange:function(newv){
app.callEvent("detailsModeChanged", [newv]);
}
}
};

We can simplify it in the following way:

var mode = {
view:"segmented", options:[
{id:"info", value:"Info"},

19
Advanced Techniques

{id:"stats", value:"Stats"}
],
on:{
onChange:app.action("detailsModeChanged");
}
}

It's also possible to replace the $oninit property and the attachEvent method with the $onevent
property. Thus, the above code of datatable initialization

//views/data.js

...
return {
$ui: { view:"datatable" },
$oninit:function(view, $scope){
$scope.on(app, "detailsModeChanged", function(mode){
view.showColumnBatch(mode);
});
}
}

can be shortened as in

...
return {
$ui: { view:"datatable", id:"data:table" },
$onevent:{
detailsModeChanged: function(mode){
$$("data:table").showColumnBatch(mode);
}
}
}

In the sample above view variable is not accessible, so we need to address the datatable by id.

Declaring and calling methods

One more effective way of views connecting is the usage of methods. In one of the views we define a
handler that will call some function and in another view we call this handler.

Unlike events, methods not only call actions in views, but also can return something useful. However,
this variant can only be used when we know that a view where a necessary method is declared exists.
It's better to use this variant when there is a parent view and a child one. A method is declared in the
child view and is called in the parent one.

Let's have a look at the example below:

// views/films.js

return {
$ui:{ view:"datatable", id:"films:table"},
truncateAll:function(){
$$("films:table").clearAll();
20
Advanced Techniques

},
getActiveRecord:function(){
var id = $$("films:table").getSelectedId();
return id? $$("films:table").getItem(id): null;
}
}

The code of a view creates an already mentioned datatable. It this view we declare the truncateAll()
method which defines a function for clearing the datatable's content. Additionally, the
getActiveRecord method specifies a function that returns a record selected in the datatable.

In another view we have the following code:

// views/data.js

define([
"views/films"
],function(films){

var details = { view:"template", id:"data:tpl", data:{}, template:


return obj.id?obj.rank+obj.title;
}};

var ui = {
rows:[
{view:"toolbar", elements:[
{ view:"button", value:"Show details", click:function
var item = films.getActiveItem();
if(item){
$$("data:tpl").data = item;
$$("data:tpl").refresh();
}},
{ view:"button", value:"Clear All", click:function(){
films.truncateAll();
}}
]},
films,
details
]
};

return {
$ui: ui
};

});

Here we specify a toolbar with two buttons and detailed film view and then place everything in three
rows together with the datatable from the child view.

By clicking the first button we get an object of the active datatable record and use it for details view
while the second button calls the truncateAll() method which clears the datatable in the child view.

The use of methods for connecting views is presented in the demo.


21
Advanced Techniques

Using a shared state

There's one more way to organize views communication. It's possible to create a separate module that
will keep a state that is common for other views. For example, when we select some item in one view,
its id will be kept in the state module and another view will use this id. Thus, if we rerender the
second view after some time, it will take the current id from the state module and display correct data.

Let's consider a more concrete case. In the Triggering events section we dealt with two views: one
with a segmented button that switches data mode and one more view with a datatable to which this
mode is applied. They are connected with detailsModeChanged event, but when you reload the view
(change from "#!/top/data" to "#!/top/start" and back to top) current data mode is not applied to the
datatable.

To establish a permanent connection between the button and the datatable, we will define a separate
model file where dataMode will be stored.

// models/state.js

define([], function(){
return {
dataMode:"info"
};
});

//views/data.js

define([
"models/state"
], function(state){
return {
$ui:{ view:"datatable"},
$oninit:function(view){
view.parse(records.data);

view.showColumnBatch(state.dataMode);
app.attachEvent("detailsModeChanged", function(){
view.showColumnBatch(state.dataMode);
});
}
}
});

//views/top.js
define([
"models/state"
], function(state){

var mode = {
view:"segmented", value:state.dataMode, options:[
{id:"info", value:"Info"},
{id:"stats", value:"Stats"}
],
on:{
onChange:function(newv){
22
Advanced Techniques

state.dataMode = newv;
app.callEvent("detailsModeChanged");
}
}
};
});

When we switch a segment in the button, the code stores the selected segment's id in the model. When
we need to access this info in the $oninit or "detailsModeChanged" handler of the datatable, we can
retrieve it from the same model object. For example, in the above snippet, we will apply the current
state to a datatable each time it is reloaded and each time another mode is selected.

Check the demo that shows the way of connecting views using a shared state.

Which way to choose?

Let's summarize when it's better to use this or that way of creating connections between views.

Events are helpful when one view doesn't know that another view exists, but it should react to the
action that takes place in this view.

Methods are handy when there is a view enclosed into another one. This way allows you to do some
actions in the child view and return some data.

The usage of a module with a shared state allows keeping some state common for several views in a
separate file. This way is rather useful, as different views can have access to common data. You
should be aware which views use the same state, as changes made in the state module will affect them
all.

Asynchronous UI loading
Sometimes we can include data used in a view directly in the view description. In such a case we deal
with the so-called "hardcoded" values. For example, we have a chart on the start page and need to
define the colors of its lines, specified in the series parameter:

//views/start.js

define([
"models/records"
],function(records){

var ui = {
view:"chart",
series:[{ value:"#sales#", color:"#1293f8"},{value:"#sales2#"
...
};

return {
$ui:ui,
$oninit:function(){
view.parse(records.data);
}
};

23
Advanced Techniques

});

However, in practice some configuration settings in our UI can be stored in the database. For example
in the above snippet we may want to store colors in DB to allow their customization by the end user.
(Replace hardcoded values with DB based ones)

In such case, a module can return a promise of UI instead of UI configuration.

Let's initialize such a chart in the view/data.js file with the help of the code below:

//view/data.js

define([
"models/records"
],function(records){
return webix.ajax("colors.php").then(function(data){
data = data.json();
//[{"id":1,"color":"#1293f8"},{"id":2,"color":"#66cc00"}]

var ui = {
view:"chart",
series:[
{ value:"#sales#", color:data[0].color},
{ value:"#sales2#", color:data[1].color}
],
...
};

return {
$ui:ui,
$oninit:function(){
view.parse(records.data);
}
};
});
});

In the above code all the colors used for lines of the chart are stored in DB and are returned by
"server/colors.php" script. webix.ajax call sends an asynchronous request to the "server/colors.php"
script on the server and returns promise of data instead of real data. First, all data should come to the
client and only after that final view configuration will be constructed and the view will be rendered.

There are several ways to implement asynchronous data loading:

webix.ajax that makes an asynchronous request to a PHP script and shows it response through a
callback function;
data.waitData which is used for data components, such as datacollection, list, tree, datatable,
etc;
webix.promise that allows treating the result of asynchronous operations without callbacks.

The demo will help you to learn the details of asynchronous UI loading with Webix Jet.

Multi-language support
24
Advanced Techniques

You can easily localize your application by using a special plugin. All you need is to include the
locale.js file into the app.js and add the app.use(locale) line in the configuration:

// app.js
define([
"libs/webix-mvc-core/plugins/locale"
]),function(locale){
// configuration
...
app.use(locale);
});

In the view which needs to be localized the "locale" dependency should be included. This dependency
will provide the translating function. All the text values that need to be translated must be wrapped
into this function:

//views/input.js
define(["locale"], function(_){
return {
$ui:{ view:"text", value: _("Language") }
}
});

The call of the translating function will return the text in the specified locale.

How locales are stored

Files of locales are stored as locales/language.js.

The format of files is the following:

// locales/en.js
define ([], function(){
return { "Language": "Language" };
});

// locales/de.js
define([],function(){
return { "Language": "Sprache"};
});

As you can see, this is a collection of key:value pairs, where the key is the name of the text string used
in a view file, and the value is the translation.

Setting the language

By default, English locale is set. You can specify the default language in the app.js file.

app.use(locale, { lang:"de" });

To change the active language of the app, use the setLang() method. It takes a two-letter code of the
language as the argument:

locale.setLang("de");

25
Advanced Techniques

It's also possible to return the currently set language, using the getLang() method:

locale.getLang();

Benefits of localization

The basis of Webix Jet localization is Polyglot.js library. Besides usual words substitution, it can
insert necessary parameters into a line as well as create phrases in the right form depending on the
number of a noun.

To get a pluralized phrase, a special string is used. The plural forms are separated by the delimiter
"||||", or four vertical line characters.

define(["models/records", "locale"], function(records, _){


var count = records.data.count();
return {
$ui:{ template:_("FilmsCountLabel", count) }
}
});

//locale/en.js
define(function(){
return {
FilmsCountLabel:"You have %{smart_count} film |||| You have %{smart_co
}
});

To understand the difference better, check the next example:

var label = _("FilmsCountLabel", 1); // You have 1 film


var label = _("FilmsCountLabel", 6); // You have 6 films

You can find more information in the documentation of Polyglot.js.

The demo will give you the idea how localization can be implemented in an application.

The structure of URL and folders


A typical URL looks as http://some.com/#!/top/child/subchild. The part that goes after #! is the
current state of the app. Each segment is the name of a file from the views directory.

The order of views rendering in the above url is the following:

views/top.js is rendered
if there is some space for a child element (subview) in top.js, views/child.js is taken and
rendered in the defined place
if child.js has some space for its own child element, views/subchild.js is rendered in the defined
place

If you need to include a file from a subdirectory, e.g. details/subchild.js instead of a views/subchild.js,
the dot notation is used: http://some.com/#!/top/child/details.subchild.

Let's consider more examples:

The /top/start directory implies that two views will be loaded:


26
Advanced Techniques

views/top.js
views/start.js

The path /top/start/details.descr has the following views hierarchy:

views/top.js
views/start.js
views/details/descr.js

A more complex structure can look as /top/data.films/data.about.1. It includes the following


directories and subdirectories:

views/top.js
views/data/films.js
views/data/about/1.js

Check the demo to see how URL is formed in case files from subfolders are used in an application.

Using unique IDs


Webix components can have ids. They are used to refer to this or that component, which is rather
handy. The important moment is that ids must be unique ( that is requirement of Webix UI, as code
can't distinguish between two views with the same id ). There are three ways to make a unique id:

create a complex id that have the structure {viewname}:{role}, for example: "start:view". By
using the view's name (which is the name of the file where it is stored) in the 1st part of id we
make it unique;
generating a unique id with the webix.uid() method:

define([
"models/records"
],
function(records){
var select_id = webix.uid();
var button_id = webix.uid();

var ui = {
cols:[
{ view:"richselect", id:select_id, options:{
body:{ template:"#title#", data:records.data }
}},
{ view:"button", id:button_id, value:"Clear", click:function
$$(select_id).setValue("");
}}
]
};

return {
$ui:ui
};
});

Here we have a layout that contains a select box and a button that calls a function to clear it. Each
time the code creates a layout, it generates a new id for both of them.
27
Advanced Techniques

While you can get a view by its id from any module of your app, it is strongly recommended to call
an element by id only inside of the same module where this element was defined.

isolated ID spaces

define([
"models/records"
],
function(records){

var ui = {
isolate:true, cols:[
{ view:"richselect", id:"selectbox", options:{
body:{ template:"#title#", data:records.data }
}},
{ view:"button", id:"clear_selection", value:"Clear", click:
var selectbox = this.getTopParentView().$$("selectbox"
selectbox.setValue("");
}}
]
};

return {
$ui:ui
};
});

The above code contains an isolated layout that includes a select box and a button. By clicking the
button, the selection is cleared. As our layout is located in an isolated space, we need to call the
getTopParentView() method to refer to the isolated id "selectbox" to call the setValue method, as there
can be the same global ids.

You can check the demo illustrating how isolated spaces are applied for making IDs.

Creating unique IDs for multiple displaying of a view

Let's consider the case when it's necessary to show the same view on the screen several times.

We can't directly specify the id of the view as it's made in the first variant, because each separate
instance of the view must have a unique id. The second variant implies that a unique id will be
generated just once, which is also improper for multiple displaying of a view.

As for the third variant, it will do fine for our needs. By using isolated ID spaces you can repeatedly
use the same view on the page, as isolated ids will be treated as unique.

There is one more way. We can specify a function that will return a view with a new id each time the
code is executed:

define([
"models/records"
],
function(records){

var select_id = webix.uid();

28
Advanced Techniques

var button_id = webix.uid();

return {
$ui: (function(){
return {
cols:[
{ view:"richselect", id:select_id, options:{
body:{ template:"#title#", data:records.data }
}},
{ view:"button", id:button_id, value:"Clear", click:
$$(select_id).setValue("");
}}
]
};
})()
};
});

Thus, the above code will create each new instance of a richselect view with a unique id.

The demo of the described functionality is presented here.

29
And Some More

And Some More


Building and deploying an application
You need to have Node.js installed to use the build system. To start the building process, run the
following command from the project’s directory:

npm install

If you want to check all js code, run the next command:

gulp lint

To build the final app to deploy on the server, use

gulp build

The deployed application consists of the following parts:

app.js - includes all the views and models files; the file is as small as 10kb
app.css - contains all the styles applied in the app
index.html - the index page of an app
assets/ - the used styles and images

What to do if you've modified the code, but nothing changes on the screen

When you refresh a page in the browser, the changes you’ve made in the app configuration may not
display. It happens because a browser tries to cache the repeatedly used files in case there are a lot of
them in the code. To solve this issue, we recommend to disable caching in the Developer tools of your
browser during the development process.

Complex navigation
Besides simple urls used for navigation between views, we can create more complex urls that will
allow restoring the app’s state after the page's reloading.

Each segment of an url can contain certain parameters. These parameters can keep the id of the
selected record, the mode of a segmented button that determines the state of the application, etc.

For example, if we select a row with the id "1" in the grid in the parent view and check some
checkbox in the child view, the url will look as follows:

"index.html#!/top:id=1/child:checked=true"

In general, if it’s needed to render a view with selected data and show a subview inside of it, we can
use the this.$scope.show() method and pass the id of the record that should be selected as an
argument:

// /data
this.$scope.show({ id:1 });

In the url of the page the id of the selected record follows the view name and then the subview name

30
And Some More

goes:

“index.html#!/data:id=1/form"

It's possible to pass several parameters of selected records:

// /data
this.$scope.show({ id:1, task:2});

In this case the above url will have the following format:

“index.html#!/data:id=1:task=2/form"

this.$scope.show() allows reloading just the part of the app. If the app should be changed
starting from the top view, this.$scope.show() and app.show() methods can be used with no
difference:

// "data" is the top view, the result of both methods' work will be the same
this.$scope.show("/data:id=3/form");
app.show("/data:id=3/form");

To restore the state of the app after the page’s reloading, the $onurlchange handler is used.

For example, we have a layout with datatable and form and we want to reload the datatable with a
selected record kept. So, we'll specify the '$onurlchange' handler after its definition and process the id
parameter in it:

define([
"models/records"
],function(records){
var ui = {
rows:[
{ view:"datatable", id:"data:table", select:true, on:{
onAfterSelect:function(id){
this.$scope.show({id:id.row});
}
}},
{ $subview:true } //place for the form to render
]

};

return:{
$ui:ui,
$onurlchange:function(config, url, scope){
if (config.id && $$("data:table").exists(config.id))
$$("data:table").select(config.id);
}
}
});

The handler will be called each time the url changes and restore the state of the view after reloading
the application.

You can check the demo showing how parameters are passed in an url.
31
And Some More

32

Вам также может понравиться