Building Apps with Webix Jet
Version 1.x
Links
https://www.gitbook.com/book/webix/webix-jet/details
https://github.com/webix-hub/webix-adminapp-demo
https://github.com/webix-hub/webix-jet
https://github.com/webix-hub/jet-demos
Docs
Big demo
Sources
Sources
Prerequisites
- nodeJS
- used for toolchain
- https://nodejs.org/en/
- yarn
- same as npm but a bit better
- https://yarnpkg.com/en/docs/install
ES6
class Box extends Shape {
methodA(a){
webix.confirm(a, function(){ this.done(); });
}
methodB(b){
webix.confirm(b, () => this.done() );
}
done(){
webix.alert("Done");
}
}
const C = 123;
export Box;
export default C;
import {Box, C} from "some";
export AnyName from "some"; //AnyName == C
First Start
- close the demo app
git clone https://github.com/webix-hub/jet-start
- install toolchain an start the app
cd jet-start
yarn install
yarn start
- open app in the browser
http://localhost:8080
Tools
- check code quality
yarn lint
- get files for deploy
yarn build
Webpack's Magic
Automatic app updates
Check console for build errors
The App !
Codebase
/sources/myapp.js
/ sources/views
/sources/models
/sources/locales
/sources/styles
- app's config
- UI chunks
- data and data related logic
- localization
- css, images, etc.
Views
View - structure
export default {
template:"Start page"
};
export default class TopView extends JetView{
config(){
return {
type:"line", cols:[
{ view:"sidebar" }
{ template:"Some here"}
]
};
}
}
/views/start.js
/views/top.js
View - structure
import {JetView} from "webix-jet";
import {data} from "models/records";
export default class DataView extends JetView{
config(){
return { view:"datatable", autoConfig:true };
}
init(view){
view.parse(data);
}
}
/views/data.js
Classes
Objects
VS
Simple
Lifetime handlers
Custom methods
Local variables
Extending
Local Methods
export default class TopView extends JetView{
config(){
return {
view:"button", click:() => this.doClick("a")
};
},
doClick(){
alert("clicked");
}
}
Local Variables
export default class TopView extends JetView{
config(){
return {
view:"button", click:() => this.doClick("a")
};
},
init(){
this._counter = 0;
}
doClick(){
this._counter++;
alert("clicked " + this._counter);
}
}
Extending
export default class MyTopView extends TopView{
doClick(){
alert("Something different");
}
}
Why views are necessary
Composable blocks
Error resistant
Code reusage
Views - composition
export default {
template:"Start page"
};
import start from "views/start";
export default class TopView extends JetView{
config(){
return {
type:"line", cols:[
{ view:"sidebar" }
start
]
};
}
}
/views/start.js
/views/top.js
Views - routing
// url : localhost:8080/#!/top/start
export default class TopView extends JetView{
config(){
return {
type:"line", cols:[
{ view:"sidebar" }
{ $subview:true }
]
};
}
}
Views - routing
index.html#!/top/data
index.html#!/top/top/data
index.html#!/data
index.html#!/top
Views - routing
HashRouter
index.html#!/top/data
UrlRouter
index.html/top/data
StoreRouter
EmptyRouter
Benefits
Can use Navigation keys in browser
Refresh friendly app
Nice for development
Navigation
export default {
template:'<a href="#!/top/data">Details</a>'
}
export default {
template:'<a route="/top/data">Details</a>'
}
export default {
view:"button", click:() => this.show("/top/data")
}
// From top.js this.show("./data2")
// From data.js this.show("../data2")
Parameters
export default {
template:'<a href="#!/top/data?id=3">Details</a>'
}
import start from "views/start";
export default class TopView extends JetView{
config(){
view:"text"
}
init(view, url){
view.setValue(url[0].params.id);
}
}
Referencing views
export default class TopView extends JetView{
config(){
return { view:"button", click:() => {
this.doSome();
}}
},
doSome(){ alert("done"); }
}
export default class TopView extends JetView{
config(){
return { view:"button", click:function(){
this.$scope.doSome();
}}
},
doSome(){ alert("done"); }
}
Referencing views
export default class TopView extends JetView{
config(){
return { rows:[
{ view:"button", some:1 },
{ view:"button", some:2 },
] };
}
init(view){
// top level view
view.addView({ template:"some" });
// button
view.queryView({ some:2 }).hide();
}
}
The app
import "./styles/app.css";
import {JetApp} from "webix-jet";
webix.ready(() => {
var app = new JetApp({
id: APPNAME,
version: VERSION,
start: "/top/start",
debug: true
});
app.render();
});
Lets try it!
Data
export default class DataView extends JetView {
config(){
return { view:"datatable", autoConfig:true, data:[
{ a:1, b:2 }
]}
}
});
BAD
import {someData} from "models/some"
export default class DataView extends JetView {
config(){
return { view:"datatable", autoConfig:true }
}
init(){
view.parse(someData);
}
});
GOOD
Shared Data
//models/records
const data = new webix.DataCollection({ data:[
{ id:1, title:"The Shawshank Redemption", year:1994 },
/*...*/
]});
export function getData(){ return data; };
import {getData} from "models/records"
export default class DataView extends JetView {
config(){
return { view:"datatable", autoConfig:true }
}
init(){
view.parse(getData());
}
});
Shared Data - Saving
//models/records
const data = new webix.DataCollection({
url:"/some/records", save:"rest->/some/records"
]});
export function getData(){ return data; };
Dynamic Data
//models/records
export function getData(){
return webix.ajax("data.php");
};
import {getData} from "models/records"
export default class DataView extends JetView {
config(){
return { view:"datatable", autoConfig:true }
}
init(){
view.parse(getData());
}
});
Dynamic Data
//models/records
export function getData(){
return webix.ajax("data.php");
};
export function saveData(id, operation, data){
return webix.ajax().put("/some/records", data);
};
import {getData, saveData} from "models/records"
export default class DataView extends JetView {
config(){
return { view:"datatable", autoConfig:true, save:saveData }
}
init(){
view.parse(getData());
}
});
Remote models
import {getData} from "models/records"
export default class DataView extends JetView {
config(){
return {
view:"datatable", autoConfig:true,
url:"/some/records"
}
}
});
Huge Data - Saving
import {getData} from "models/records"
export default class DataView extends JetView {
config(){
return {
view:"datatable", autoConfig:true,
url:"/some/records", save:"rest->/some/records"
}
}
});
Relative small
Used many times
Shared Data
Relative big
Used once
Dynamic Data
AND
OR
Huge
Prototyping
Remote Models
OR
Be pragmatic!
Communication between Views
- parameters
- events
- services
- by globals
- parent methods
- by ID
Parameters
export default class DataView extends JetView {
config(){
return { rows:[
{ $subview:true }
]}
}
init(){
this.show("./form?id=1")
}
});
export default class FormViewextends JetView {
config(){
...
}
urlChange(view, url){
if (url[0].params.id){
this.getRoot().setValues( getData(id) )
}
}
});
Events
export default class DataView extends JetView {
config(){
return { rows:[
{ $subview: true }
]}
}
init(){
this.app.callEvent("do:clear:form")
}
});
export default class FormViewextends JetView {
init(){
this.app.on("do:clear:form", () => this.clear() )
}
clear(){
this.getRoot().setValues({ a:1 })
}
});
Services
export default class DataView extends JetView {
config(){
return { rows:[
{ $subview: true }
]}
}
init(){
this.app.getService("form").clear();
}
});
export default class FormViewextends JetView {
init(){
this.app.addService("form", this)
}
clear(){
this.getRoot().setValues({ a:1 })
}
});
Parameters
Initialization
Data loading
Events
Services
Actions
Multiple receivers
Bottom -> Up
State requests
Any direction
Single service view
Popups
export default class DataView extends JetView {
config(){
return {
view:"button", click:() => this._popup.show()
}
},
init(){
this._popup = webix.ui({ view:"window" });
}
destroy(){
this._popup.destroy();
}
});
Popups
import {PopupView} from "views/some_popup"
export default class DataView extends JetView {
config(){
return {
view:"button", click:() => this._popup.show()
}
},
init(){
//same as webix.ui
this._popup = this.ui(PopupView);
}
});
Access Control
export default class DataView extends JetView {
config(){
if (this.app.config.access != "writer"){
return { };
}
return { template:"Access denied" };
}
});
Access Control
app.attachEvent("app:guard", function(url, view, nav){
if (url.indexOf("/admin") !== -1){
nav.redirect = "/top/allowed1";
}
});
View Life Cycle
export default class DataView extends JetView {
config(){}
init(){}
urlChange(){}
ready(){}
destroy(){}
});
Plugins
export default class DataView extends JetView {
init(){
this.use(plugins.Menu)
}
});
View
App
- Menu
- UnloadGuard
- Status
- User
- Locale
- Theme
Localization
//app.js
import {plugins} from "webix-jet";
app.use(plugins.Locale);
//view.js
export default class SettingsView extends JetView {
config(){
const _ = this.app.getService("locale")._;
return {
type:"space", rows:[
{ template:_("Settings"), type:"header" },
Error handling
var app = new JetApp({
debug: true // console.log and debugger on error
});
app.attachEvent("app:error", function(err){
alert("Error");
});
app.attachEvent("app:error:resolve", function(err, url) {
webix.delay(() => app.show("/some"));
});
This is all for now!
mkozhukh@ya.ru
What was missed ?
- App level events
- Fixed subviews
- Named subviews
- App as view
- Skins plugin
- User plugin
- Using webix.remote
- Custom routers
- View decorators
- Async views
Building Apps with Webix UI
By mkozhukh
Building Apps with Webix UI
- 4,443