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,315