Webfx
Web UI framework and utilities.
It was originally created for MusicCloud.
Demos
-
And of cource MusicCloud (GitHub repo)
Project Structure
-
utils.ts
- Utilities -
i18n.ts
- Internationalization (i18n) helper -
buildDOM.ts
- View and DOM builder -
view.ts
- The core of View -
views/
- Some built-in Views and helpersBasics
ListView
Dialog
Menu
Overlay
Toast
LoadingIndicator
Section
-
style.css
- CSS for the built-in views -
dist/
- Bundles-
webfx.js
- UMD bundle for browsers (+.min.js
version) -
webfxcore.min.js
- UMD bundle without the viewlib and style -
webfx.esm.js
- ESM bundle
-
Installation
Import from modules
Install the module locally using npm
:
npm install @yuuza/webfx
Import from ES modules:
import { View, ButtonView } from "@yuuza/webfx";
Load into the browser directly
Add webfx to your web page:
<script src="https://cdn.jsdelivr.net/npm/@yuuza/webfx@1.9.12/dist/webfx.min.js"></script>
Or if you don't need the viewlib:
<script src="https://cdn.jsdelivr.net/npm/@yuuza/webfx@1.9.12/dist/webfxcore.min.js"></script>
Then the module can be accessed from global variable webfx
:
const { View, ButtonView, mountView } = webfx;
Usage
A basic view component
// Define a very basic view class
class Hello extends View {
createDom() {
// Returns a so-called "DOM expression" object.
return {
tag: 'p.text.bold#hello',
text: 'hello webfx'
}
}
}
// Create a instance of the view and mount it on <body>.
mountView(document.body, new Hello());
Renders:
<p class="text bold" id="hello">hello webfx</p>
Note: createDom()
can be omited, then the DOM will be an empty <div>
.
DOM Expression
A DOM Expression is a BuildDomNode
object, View
object, string, number or function (which returns string/number only).
type BuildDomNode = {
tag?: BuildDomTag; // A string indicates DOM tag name, class names and id, similar to CSS seletor.
child?: BuildDomExpr[] | BuildDomExpr; // One or more DOM expressions as the children.
text?: FuncOrVal<string>; // Shortcut for `textContent`, can be a function, see below.
hidden?: FuncOrVal<boolean>; // `hidden` but can be a function, see below.
init?: Action<HTMLElement>; // A callback that is called on the DOM created.
update?: Action<HTMLElement>; // A callback that is called on the View updated.
// ...omited internal properties...
}
The text
and hidden
callbacks will be called in updateDom()
.
Properties and Child Elements
There are no state
s in webfx. They're just properties.
Use the child
key in DOM expression for child elements.
webfx.injectWebfxCss();
class Counter extends View {
constructor() {
super();
this.count = 0;
}
createDom() {
return {
tag: 'div.counter',
child: [
'Count: ', () => this.count, {tag: 'br'},
new ButtonView({ text: 'Click me', onActive: () => {
this.updateWith({count: this.count + 1});
}})
]
}
}
}
mountView(document.body, new Counter());
Renders:
<div class="counter">
Count: 9<br>
<div class="btn" tabindex="0">Click me</div>
</div>
Hook methods
postCreateDom()
is called when the DOM is just created.
updateDom()
is called after postCreateDom()
, also be called manually by updateDom()
.
updateWith()
is a shortcut for changing properties and calling updateDom()
.
Note: Remember to call the super
method when overriding these methods.
webfx.injectWebfxCss();
class Counter extends View {
constructor() {
super();
this.count = 0;
}
createDom() {
return {
tag: 'div.counter',
child: [
'Count: ',
{
tag: 'span',
text: () => this.count,
init: (dom) => { console.info('the <span> DOM is created', dom); },
update: (dom) => { dom.style.fontSize = `${14 + this.count}px`; }
},
{tag: 'br'},
new ButtonView({text: 'Click me', onActive: () => {
this.updateWith({count: this.count + 1});
}})
]
}
}
postCreateDom() {
super.postCreateDom();
console.info('the counter DOM is created', this.dom);
}
updateDom() {
super.updateDom();
console.info('the counter DOM is updated', this.dom);
}
}
mountView(document.body, new Counter());
ListView
(TBD)
JSX/TSX
Some configuration is required to make the JSX/TSX compiler use the correct JSX factory. Set "jsxFactory": "jsx"
in tsconfig.json
or use /** @jsx jsx */
.
/** @jsx jsx */
import { View, jsx } from "@yuuza/webfx";
webfx.injectWebfxCss();
class Counter extends View {
constructor() {
super();
this.count = 0;
}
createDom() {
return (
<div class="counter">
Count: {() => this.count}<br/>
<ButtonView onActive={() => {
this.updateWith({count: this.count + 1});
}}>
Click me
</ButtonView>
</div>
);
}
}
mountView(document.body, new Counter());
I18n Helper
Using the tagged templates feature, the i18n is very easy.
This feature is also available as a standalone package @yuuza/i18n
.
import { i18n, I } from "@yuuza/webfx";
i18n.add2dArray([
['en', 'zh'],
['Hello!', '你好!'],
['My name is {0}.', '我的名字是 {0}。']
]);
function sayHello(name) {
console.log(I`Hello!`);
console.log(I`My name is ${name}.`);
}
i18n.curLang = 'en';
sayHello('Yuuza');
// Hello!
// My name is Yuuza.
i18n.curLang = 'zh';
sayHello('Yuuza');
// 你好!
// 我的名字是 Yuuza。
Todos
- [ ] Functional DOM tree updating
- [ ] React-like function components with hooks
(TBD)