Plataforma Objectum Javascript

Si desea una forma fácil de crear aplicaciones web utilizando solo javascript (pila completa), le sugiero que se familiarice con la plataforma objectum. La nueva versión de la plataforma es el resultado de la experiencia de trabajar en la versión anterior, que ha estado en uso durante 10 años. Ambas versiones se utilizan en el desarrollo de varios sistemas de información; se trata de soluciones y sistemas regionales para organizaciones. La plataforma de la nueva versión ya se está utilizando en servidores de producción y se desarrollará durante mucho tiempo. Más detalles.



logo



Capturas de pantalla de desarrollos existentes



Aplicación web de muestra:






Ejemplo 1






Sitio de ejemplo (sin renderización del servidor):






Ejemplo 2






Paquetes de plataforma



La plataforma consta de los siguientes paquetes npm: objectum , objectum-client , objectum-proxy , objectum-react , objectum-cli



objectum



, . ORM PostgreSQL. (Objectum Database Engine), PL/pgSQL. Redis. node cluster. .



objectum-client



objectum-proxy objectum. , , . , , .. , JSON.



objectum-proxy



objectum objectum-client. :



  • . — CRUD SQL.


objectum-react



react UI. bootstrap fontawesome. redux, mobx .

App.js. bootstrap.css:



import "objectum-react/lib/css/bootstrap.css";
import "objectum-react/lib/css/objectum.css";
import "objectum-react/lib/fontawesome/css/all.css";


objectum-cli



. , . .





, , React. . , objectum-react.

objectum. , NodeJS, PostgreSQL Redis.

- catalog .






catalogar








:



npm i -g objectum-cli


/opt/objectum



mkdir /opt/objectum
objectum-cli --create-platform --path /opt/objectum


:



--redis-host 127.0.0.1 -     Redis
--redis-port 6379
--objectum-port 8200 - ,    objectum




"catalog":



objectum-cli --create-project catalog --path /opt/objectum


:



--project-port 3100 - ,    
--db-host 127.0.0.1 -     PostgreSQL
--db-port 5432
--db-dbPassword 1 -   catalog
--db-dbaPassword 12345 -   postgres
--password admin -   


create-react-app. ES Modules.





:



cd /opt/objectum/server 
node index-8200.js


:



cd /opt/objectum/projects/catalog 
node index-3100.js
npm start


http://localhost:3000

, : admin






auth








"Objectum" :



  • — SQL





auth








UI, objectum-cli.



JSON



cd /opt/objectum/projects/catalog  
objectum-cli --import-json scripts/catalog-cli.json --file-directory scripts/files


catalog-cli.json
{
    "createModel": [
        {
            "name": "Item", 
            "code": "item"
        },
        {
            "name": "Item", 
            "code": "item",
            "parent": "d"
        },
        {
            "name": "Type", 
            "code": "type",
            "parent": "d.item"
        },
        {
            "name": "Item", 
            "code": "item",
            "parent": "t"
        },
        {
            "name": "Comment", 
            "code": "comment",
            "parent": "t.item"
        }
    ],
    "createProperty": [
        {
            "model": "d.item.type", 
            "name": "Name", 
            "code": "name",
            "type": "string"
        },
        {
            "model": "t.item.comment", 
            "name": "Item", 
            "code": "item",
            "type": "item"
        },
        {
            "model": "t.item.comment", 
            "name": "Date",
            "code": "date",
            "type": "date"
        },
        {
            "model": "t.item.comment",
            "name": "Text",
            "code": "text",
            "type": "string"
        },
        {
            "model": "item", 
            "name": "Date", 
            "code": "date",
            "type": "date"
        },
        {
            "model": "item", 
            "name": "Name", 
            "code": "name",
            "type": "string"
        },
        {
            "model": "item",
            "name": "Description",
            "code": "description",
            "type": "string",
            "opts": {
                "wysiwyg": true
            }
        },
        {
            "model": "item", 
            "name": "Cost", 
            "code": "cost",
            "type": "number",
            "opts": {
                "min": 0
            }
        },
        {
            "model": "item", 
            "name": "Type", 
            "code": "type",
            "type": "d.item.type"
        },
        {
            "model": "item", 
            "name": "Photo", 
            "code": "photo",
            "type": "file",
            "opts": {
                "image": {
                    "width": 300,
                    "height": 200,
                    "aspect": 1.5
                }
            }
        }
    ],
    "createQuery": [
        {
            "name": "Item",
            "code": "item"
        },
        {
            "name": "List",
            "code": "list",
            "parent": "item",
            "query": [
                "{\"data\": \"begin\"}",
                "select",
                "    {\"prop\": \"a.id\", \"as\": \"id\"},",
                "    {\"prop\": \"a.name\", \"as\": \"name\"},",
                "    {\"prop\": \"a.description\", \"as\": \"description\"},",
                "    {\"prop\": \"a.cost\", \"as\": \"cost\"},",
                "    {\"prop\": \"a.date\", \"as\": \"date\"},",
                "    {\"prop\": \"a.photo\", \"as\": \"photo\"},",
                "    {\"prop\": \"a.type\", \"as\": \"type\"}",
                "{\"data\": \"end\"}",
                "",
                "{\"count\": \"begin\"}",
                "select",
                "    count (*) as num",
                "{\"count\": \"end\"}",
                "",
                "from",
                "    {\"model\": \"item\", \"alias\": \"a\"}",
                "",
                "{\"where\": \"empty\"}",
                "",
                "{\"order\": \"empty\"}",
                "",
                "limit {\"param\": \"limit\"}",
                "offset {\"param\": \"offset\"}"
            ]
        },
        {
            "name": "Item",
            "code": "item",
            "parent": "t"
        },
        {
            "name": "Comment",
            "code": "comment",
            "parent": "t.item",
            "query": [
                "{\"data\": \"begin\"}",
                "select",
                "    {\"prop\": \"a.id\", \"as\": \"id\"},",
                "    {\"prop\": \"a.item\", \"as\": \"item\"},",
                "    {\"prop\": \"a.date\", \"as\": \"date\"},",
                "    {\"prop\": \"a.text\", \"as\": \"text\"}",
                "{\"data\": \"end\"}",
                "",
                "{\"count\": \"begin\"}",
                "select",
                "    count (*) as num",
                "{\"count\": \"end\"}",
                "",
                "from",
                "    {\"model\": \"t.item.comment\", \"alias\": \"a\"}",
                "",
                "{\"where\": \"empty\"}",
                "",
                "{\"order\": \"empty\"}",
                "",
                "limit {\"param\": \"limit\"}",
                "offset {\"param\": \"offset\"}"
            ]
        }
    ],
    "createRecord": [
        {
            "_model": "d.item.type",
            "name": "Videocard",
            "_ref": "videocardType"
        },
        {
            "_model": "d.item.type",
            "name": "Processor"
        },
        {
            "_model": "d.item.type",
            "name": "Motherboard"
        },
        {
            "_model": "objectum.menu",
            "name": "User",
            "code": "user",
            "_ref": "userMenu"
        },
        {
            "_model": "objectum.menuItem",
            "menu": {
                "_ref": "userMenu"
            },
            "name": "Items",
            "icon": "fas fa-list",
            "order": 1,
            "path": "/model_list/item"
        },
        {
            "_model": "objectum.menuItem",
            "menu": {
                "_ref": "userMenu"
            },
            "name": "Dictionary",
            "icon": "fas fa-book",
            "order": 2,
            "_ref": "dictionaryMenuItem"
        },
        {
            "_model": "objectum.menuItem",
            "menu": {
                "_ref": "userMenu"
            },
            "name": "Item type",
            "icon": "fas fa-book",
            "parent": {
                "_ref": "dictionaryMenuItem"
            },
            "order": 1,
            "path": "/model_list/d_item_type"
        },
        {
            "_model": "objectum.role",
            "name": "User",
            "code": "user",
            "menu": {
                "_ref": "userMenu"
            },
            "_ref": "userRole"
        },
        {
            "_model": "objectum.user",
            "name": "User",
            "login": "user",
            "password": "user",
            "role": {
                "_ref": "userRole"
            }
        },
        {
            "_model": "objectum.menu",
            "name": "Guest",
            "code": "guest",
            "_ref": "guestMenu"
        },
        {
            "_model": "objectum.menuItem",
            "menu": {
                "_ref": "guestMenu"
            },
            "name": "Items",
            "icon": "fas fa-list",
            "order": 1,
            "path": "/model_list/item"
        },
        {
            "_model": "objectum.role",
            "name": "Guest",
            "code": "guest",
            "menu": {
                "_ref": "guestMenu"
            },
            "_ref": "guestRole"
        },
        {
            "_model": "objectum.user",
            "name": "Guest",
            "login": "guest",
            "password": "guest",
            "role": {
                "_ref": "guestRole"
            }
        },
        {
            "_model": "item",
            "name": "RTX 2080",
            "description": [
                "<ul>",
                "<li>11GB GDDR6</span></li>",
                "<li>CUDA Cores: 4352</span></li>",
                "<li>Display Connectors: DisplayPort, HDMI, USB Type-C</span></li>",
                "<li>Maximum Digital Resolution: 7680x4320</span></li>",
                "</ul>"
            ],
            "date": "2020-06-03T19:27:38.292Z",
            "type": {
                "_ref": "videocardType"
            },
            "cost": "800",
            "photo": "rtx2080.png"
        }
    ]
}


:



  • createModel — , name — , code — , parent — . .

    • item, d.item.type, t.item.comment.
    • "d.item.type" — . "item" "type". "d".
    • "t.item.comment" — . "item". "t".
  • createProperty — , name — , code — , model — , type — .. , opts — , , wysiwyg .
  • createQuery — SQL JSON , , , .

    • [] — .
    • {"...": "begin"}...{"...": "end"} SQL : (data), - (count), (where), (order), - (tree).
    • {"model": "item", "alias": "a"} "_id a".
    • {"prop": "a.name"} "a._id".
    • {"prop": "limit"} .
  • createRecord — .

    • _model —
    • name —
    • [] — .
    • _ref — .. id .
    • JSON .
    • "photo": "rtx2080.png" — . photo "rtx2080.png" scripts/files.


CSV



cd /opt/objectum/projects/catalog  
objectum-cli --import-csv scripts/stationery.csv --model item --file-directory/script/files --handler scripts/csv-handler.js
objectum-cli --import-csv scripts/tv.csv --model item --file-directory/script/files --handler scripts/csv-handler.js


CSV:



  • CSV (item)
  • ()
  • (csv-handler.js):

    • .. ()




ItemModel . ReactJS NodeJS ItemClientModel, ItemServerModel ItemModel, ItemClientModel extends ItemModel, ItemServerModel extends ItemModel.





App.js:



import ItemModel from "./models/ItemModel";

store.register ("item", ItemModel);


"item" ItemModel:



let record = await store.createRecord ({
    _model: "item",
    name: "Foo"
});


:



  • record.name = "Bar";
  • record.store
  • await record.sync ()


ItemModel.js
import React from "react";
import {Record, factory} from "objectum-client";
import {Action} from "objectum-react";

class ItemModel extends Record {
    static _renderGrid ({grid, store}) {
        return React.cloneElement (grid, {
            label: "Items", // grid label
            query: "item.list", // grid query
            onRenderTable: ItemModel.onRenderTable, // grid table custom render
            children: store.roleCode === "guest" ? null : <div className="d-flex">
                {grid.props.children}
                <Action label="Server action: getComments" onClickSelected={async ({progress, id}) => {
                    let recs = await store.remote ({
                        model: "item",
                        method: "getComments",
                        id,
                        progress
                    });
                    return JSON.stringify (recs)
                }} />
            </div>
        });
    }

    static onRenderTable ({grid, cols, colMap, recs, store}) {
        return (
            <div className="p-1">
                {recs.map ((rec, i) => {
                    let record = factory ({rsc: "record", data: Object.assign (rec, {_model: "item"}), store});

                    return (
                        <div key={i} className={`row border-bottom my-1 p-1 no-gutters ${grid.state.selected === i ? "bg-secondary text-white" : ""}`} onClick={() => grid.onRowClick (i)} >
                            <div className="col-6">
                                <div className="p-1">
                                    <div>
                                        <strong className="mr-1">Name:</strong>{rec.name}
                                    </div>
                                    <div>
                                        <strong className="mr-1">Date:</strong>{rec.date && rec.date.toLocaleString ()}
                                    </div>
                                    <div>
                                        <strong className="mr-1">Type:</strong>{rec.type && store.dict ["d.item.type"][rec.type].name}
                                    </div>
                                    <div>
                                        <strong className="mr-1">Cost:</strong>{rec.cost}
                                    </div>
                                    <div>
                                        <strong>Description:</strong>
                                    </div>
                                    <div dangerouslySetInnerHTML={{__html: `${record.description || ""}`}} />
                                </div>
                            </div>
                            <div className="col-6 text-right">
                                {record.photo && <div>
                                     <img src={record.getRef ("photo")} className="img-fluid" width={400} height={300} alt={record.photo} />
                                </div>}
                            </div>
                        </div>
                    );
                })}
            </div>
        );
    }

    // item form layout
    static _layout () {
        return {
            "Information": [
                "id",
                [
                    "name", "date"
                ],
                [
                    "type", "cost"
                ],
                [
                    "description"
                ],
                [
                    "photo"
                ],
                [
                    "t.item.comment"
                ]
            ]
        };
    }

    static _renderForm ({form, store}) {
        return React.cloneElement (form, {
            defaults: {
                date: new Date ()
            }
        });
    }

    // new item render
    static _renderField ({field, store}) {
        if (field.props.property === "date") {
            return React.cloneElement (field, {showTime: true});
        } else {
            return field;
        }
    }

    // item render
    _renderField ({field, store}) {
        return ItemModel._renderField ({field, store});
    }
};

export default ItemModel;


:



  • _renderGrid — "item" /model_list/item. ModelList, Grid.
  • _layout — /model_record/:id ModelRecord, . , , , .
  • _renderForm —
  • _renderField — . .


:






auth








index.js:



import ItemModel from "./src/models/ItemServerModel.js";

proxy.register ("item", ItemModel);


store . .



ItemServerModel.js
import objectumClient from "objectum-client";
const {Record} = objectumClient;

function timeout (ms = 500) {
    return new Promise (resolve => setTimeout (() => resolve (), ms));
};

class ItemModel extends Record {
    async getComments ({progress}) {
        for (let i = 0; i < 10; i ++) {
            await timeout (1000);
            progress ({label: "processing", value: i + 1, max: 10});
        }
        return await this.store.getRecs ({
            model: "t.item.comment",
            filters: [
                ["item", "=", this.id]
            ]
        });
    }
};

export default ItemModel;


:



getComments () {
    return await store.remote ({
        model: "item",
        method: "getComments",
        myArg: "" 
    });
}




index.js:



import accessMethods from "./src/modules/access.js";

proxy.registerAccessMethods (accessMethods);


access.js
let map = {
    "guest": {
        "data": {
            "model": {
                "item": true, "d.item.type": true, "t.item.comment": true
            },
            "query": {
                "objectum.userMenuItems": true
            }
        },
        "read": {
            "objectum.role": true, "objectum.user": true, "objectum.menu": true, "objectum.menuItem": true
        }
    }
};
async function _init ({store}) {
};

function _accessData ({store, data}) {
    if (store.roleCode == "guest") {
        if (data.model) {
            return map.guest.data.model [store.getModel (data.model).getPath ()];
        }
        if (data.query) {
            return map.guest.data.query [store.getQuery (data.query).getPath ()];
        }
    } else {
        return true;
    }
};

function _accessFilter ({store, model, alias}) {
};

function _accessCreate ({store, model, data}) {
    return store.roleCode != "guest";
};

function _accessRead ({store, model, record}) {
    let modelPath = model.getPath ();

    if (store.roleCode == "guest") {
        if (modelPath == "objectum.user") {
            return record.login == "guest";
        }
        return map.guest.read [modelPath];
    }
    return true;
};

function _accessUpdate ({store, model, record, data}) {
    return store.roleCode != "guest";
};

function _accessDelete ({store, model, record}) {
    return store.roleCode != "guest";
};

export default {
    _init,
    _accessData,
    _accessFilter,
    _accessCreate,
    _accessRead,
    _accessUpdate,
    _accessDelete
};


. :



  • _init — .
  • _accessCreate — .
  • _accessRead — .
  • _accessUpdate — .
  • _accessDelete — .
  • _accessData — getData
  • _accessFilter — SQL . . .. .


, .

, , , , admin.





. - .

index.js:



import adminMethods from "./src/modules/admin.js";

proxy.registerAdminMethods (adminMethods);


admin.js
import fs from "fs";
import util from "util";

fs.readFileAsync = util.promisify (fs.readFile);

function timeout (ms = 500) {
    return new Promise (resolve => setTimeout (() => resolve (), ms));
};

async function readFile ({store, progress, filename}) {
    for (let i = 0; i < 10; i ++) {
        await timeout (1000);
        progress ({label: "processing", value: i + 1, max: 10});
    }
    return await fs.readFileAsync (filename, "utf8");
};

async function increaseCost ({store, progress}) {
    await store.startTransaction ("demo");

    let records = await store.getRecords ({model: "item"});

    for (let i = 0; i < records.length; i ++) {
        let record = records [i];

        record.cost = record.cost + 1;
        await record.sync ();
    }
    await store.commitTransaction ();

    return "ok";
};

export default {
    readFile,
    increaseCost
};


admin.js. - (guest).

:



await store.remote ({
    model: "admin",
    method: "readFile",
    filename: "package.json"
});


React



:



  • ObjectumApp — -
  • ObjectumRoute —
  • Auth —
  • Grid —

    • tree
  • Form —
  • Tabs, Tab —
  • Fields —

    • StringField — . : textarea, wysiwyg
    • NumberField —
    • BooleanField —
    • DateField — . showTime .
    • FileField — (). .
    • DictField —
    • SelectField —
    • ChooseField — .
    • JsonField — . . JSON
    • Field — .
    • JsonEditor — JSON
  • Tooltip —
  • Fade —
  • Action —


ObjectumApp



props:



  • locale — . "ru".
  • onCustomRender —
  • username, password — (guest).


Grid



(query) (model). :





    • localStorage


Form



. , . "" . , IP- .



Action



:



  • confirm —
  • :

    • ,




createReport, XLSX . :



import {createReport} from "objectum-react";

let recs = await store.getRecs ({model: "item"});
let rows = [
    [
        {text: "", style: "border_center", colSpan: 3}
    ],
    [
        {text: "", style: "border"},
        {text: "", style: "border"},
        {text: "", style: "border"}
    ],
    ...recs.map (rec => {
        return [
            {text: rec.name, style: "border"},
            {text: rec.date.toLocaleString (), style: "border"},
            {text: rec.cost, style: "border"}            
        ];
    })
];
createReport ({
    rows,
    columns: [40, 10, 10],
    font: {
        name: "Arial",
        size: 10
    }
});


:



  • rows — (row)
  • colSpan, rowSpan — HTML
  • columns —




. . , . , , , , . :





    • catalog_dev —
    • catalog_test ( catalog_dev) —
    • catalog_" " ( catalog_dev) —


    • region_dev —
    • region_" " ( region_dev) —
    • region" " ( region" ") —
  • ,


catalog:



let $o = require ("../../server/objectum");

$o.db.execute ({
    "code": "catalog",
    "fn": "export",
    "exceptRecords": ["item"],
    "file": "../schema/schema-catalog.json"
});


exceptRecords , .



:



let $o = require ("../../../server/objectum");

$o.db.execute ({
    "code": "catalog_test",
    "fn": "import",
    "file": "../schema/schema-catalog.json"
});




— . MacBook Pro Mid 2014 (MGX82).



(model.unlogged: false):



100 (.) 1000 (.) .
-: 1, : 1 0.5 4.9 204
-: 1, : 1 0.5 4.6 215
-: 1, : 1 0.5 4.4 227
-: 3, : 1, : 1, : 1 0.5 4.8 209
-: 10, : 10 0.6 5.8 172
-: 10, : 10 0.6 7.1 140
-: 10, : 10 0.6 10.1 98
-: 30, : 10, : 10, : 10 1.2 14.7 68
-: 100 : 100 2.3 27.3 37
-: 100 : 100 2.4 24.1 42
-: 100 : 100 2.3 24.6 40
-: 300 : 100, : 100, : 100 8.9 88.3 11


(model.unlogged: true):



100 (.) 1000 (.) .
-: 1, : 1 0.5 4.3 233
-: 1, : 1 0.4 4.1 244
-: 1, : 1 0.4 3.7 268
-: 3, : 1, : 1, : 1 0.5 3.8 261
-: 10, : 10 0.5 4.1 243
-: 10, : 10 0.4 4.0 251
-: 10, : 10 0.4 4.2 239
-: 30, : 10, : 10, : 10 0.5 4.9 202
-: 100 : 100 0.6 12.4 81
-: 100 : 100 0.7 6.1 162
-: 100 : 100 0.9 7.2 140
-: 300 : 100, : 100, : 100 1.1 11.1 90




:



  • 1-
  • 2- 3-
  • 4- .


test.js



Conclusión



Consulte las páginas de inicio del paquete en github para obtener más información. Confieso que la información es escasa, intentaré complementarla. Licencia de plataforma MIT. Hay planes para desarrollar paquetes adicionales para análisis y otras áreas necesarias.

Gracias por su atención.




All Articles