Applet

English     中文

A Flutter package for hot updates and code push with JavaScript.

Features

  1. Supports the latest version of Flutter.
  2. Both the UI and logic are implemented using the JavaScript.
  3. Supports all platforms, including mobile, desktop, and web.

Getting started

An example of a simple text component. Here is the JS code:

App = {
    "type": "Container",
    "color": "#000000",
    "alignment": "center",
    "child": {
        "type": "Text",
        "data": "Flutter Applet Example",
        "maxLines": 3,
        "overflow": "ellipsis",
        "style": {
            "color": "#00FFFF",
            "fontSize": 20.0,
            "fontWeight": "bold",
            "decoration": "underline"
        }
    }
}

In Applet, the App object describes the structure of the UI. Dynamically modifying components involves changing the properties of the App object.

To convert the above JS code into UI components, you can simply use Applet(jsCode). The more detailed code is as follows:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title)
      ),
      body: Center(
        child: Applet(jsCode)
      )
    );
  }

Upon running, you will see:

applet demo

Advanced

Let's look at a more complex calculator example:

const buttons = [
    { label: '+', event: "op('add')" },
    { label: '-', event: "op('sub')" },
    { label: 'x', event: "op('mul')" },
    { label: '÷', event: "op('div')" },
    { label: '1', event: "digit(1)" },
    { label: '2', event: "digit(2)" },
    { label: '3', event: "digit(3)" },
    { label: 'C', event: "clear()" },
    { label: '4', event: "digit(4)" },
    { label: '5', event: "digit(5)" },
    { label: '6', event: "digit(6)" },
    { label: '0', event: "digit(0)" },
    { label: '7', event: "digit(7)" },
    { label: '8', event: "digit(8)" },
    { label: '9', event: "digit(9)" },
    { label: '=', event: "equal()" }
];

const grid_app = {
    "type": "GridView",
    "crossAxisCount": 4,
    "padding": "10, 10, 10, 10",
    "mainAxisSpacing": 4.0,
    "crossAxisSpacing": 4.0,
    "childAspectRatio": 1.6,
    "children": buttons.map(button => ({
        "type": "TextButton",
        "child": {
            "type": "Text",
            "data": button.label,
            "style": {
                "fontSize": 30.0
            }
        },
        "click_event": button.event
    }))
};

App =
{
    "type": "Column",
    "crossAxisAlignment": "end",
    "mainAxisAlignment": "end",
    "mainAxisSize": "max",
    "textBaseline": "alphabetic",
    "textDirection": "ltr",
    "verticalDirection": "down",
    "children": [{
        "type": "Text",
        "data": "",
    }, {
        "type": "Text",
        "data": "          ",
        "maxLines": 3,
        "overflow": "ellipsis",
        "style": {
            "color": "#000000",
            "fontSize": 40.0,
            "fontWeight": "bold",
        }
    },
    {
        "type": "Expanded",
        "child": grid_app
    }]
}

var _pending = 0;
var _pendingOp = 'none';
var _display = 0;
var _displayLocked = false;
var _afterEquals = false;

function _resolve() {
    if (_pendingOp === 'add') {
        _display = _pending + _display;
    } else if (_pendingOp === 'sub') {
        _display = _pending - _display;
    } else if (_pendingOp === 'mul') {
        _display = _pending * _display;
    } else if (_pendingOp === 'div') {
        _display = _pending / _display;
    }
    _pendingOp = 'none';
}

function clear() {
    _pending = 0;
    _pendingOp = 'none';
    _display = 0;
    _displayLocked = false;
    _afterEquals = false;
    App.children[1].data = ""
}

function digit(n) {
    if (_displayLocked || _afterEquals) {
        _display = 0;
        _displayLocked = false;
        _afterEquals = false;
    }
    _display = _display * 10 + n;
    App.children[1].data = _display + "          ";
}

function op(operation) {
    _resolve();
    if (_afterEquals) {
        _pending = _display;
        _afterEquals = false;
    } else {
        _pending = _display;
    }
    _pendingOp = operation;
    _display = 0;
    _displayLocked = false;
    App.children[1].data = (operation === 'add' ? '+' : operation === 'sub' ? '-' : operation === 'mul' ? 'x' : '÷') + "          ";
}

function equal() {
    _resolve();
    _pending = _display;
    _displayLocked = true;
    _afterEquals = true;
    App.children[1].data = _display + "          ";
}

The code for the calculator can be divided into three parts:

  1. The calculator's buttons (grid components) grid_app are generated through the buttons array.
  2. In the App, the text component and grid_app are assembled to form the complete calculator UI.
  3. The calculator’s computation logic is completed through functions such as clear, digit, and equal.

Upon running, you will see:

applet_calculator

Additional information

  1. The inspiration for the calculator comes from rfw.
  2. The UI components of the applet are built using dynamic_widget.
  3. The JS runtime uses jsf, which I developed myself.

Libraries

applet