Overview of a simple framework for creating single-page applications Matreshka.js. The post uses ECMAScript 2017, which can be rewritten to ECMAScript 5.
The bindNode
function binds a property and an element.
const object = { name: 'Brendan' }; const node = document.querySelector('.name'); Matreshka.bindNode(object, 'name', node); object.name = 'Doug';
If the property changes, the element changes, if the element changes (for example, the user enters text), the property changes. Out of the box Matreshka.js can handle any form elements. You can declare a binding for arbitrary elements, see the documentation .
The calc
function associates one property with others.
Matreshka.calc(object, 'fullName', ['firstName', 'lastName'], (firstName, lastName) => { return `${firstName} ${lastName}` }); object.firstName = 'Brendan'; object.lastName = 'Eich'; // ... console.log(object.fullName); // "Brendan Eich"
When a source property changes ( firstName
or lastName
), the target property changes ( fullName
).
Together with the bindNode
function, bindNode
can declare long dependency chains: property a
depends on the state of the element e1
, property b
depends on property a
, property c
depends on elements e2
and e3
and on property b
, whose change, as a result, changes e1
, e2
and e3
...
Such dependencies can be represented as a table in a spreadsheet processor (for example, Excel): at each moment in time you think about one formula, and not about the multiple links of all the cells. As a result, you get fewer bugs, since you need to think about atomic entities, and not about the whole "table" (application). More information in the documentation .
The on
function catches and trigger
generates events.
Matreshka.on(object, 'something', () => alert('something is happened')); Matreshka.trigger(object, 'something');
You can listen to changes in properties in order to call custom code. Thus, you can add, say, a fetch query to a dependency chain.
Matreshka.on(object, 'change:fullName', async () => { await fetch('/api/name', { method: 'post', body: this.fullName }); // ... })
Events in Matreshka.js is a separate topic. You can declare delegated object events (listen for changes in the depth of the object tree), DOM events, delegated DOM events, etc. Read a detailed article on this topic.
The mediate
function adds runtime validation and conversion of property values. For example, you can declare a property of a specific type, you can limit a property to a specified range, you can generate an exception, if necessary.
Matreshka.mediate(object, 'x', x => String(x)); object.x = 42; console.log(object.x, typeof object.x); // "42", "string"
Instances of the Matreshka
class contain the same methods that work with an object, with the difference that you don't need to pass an object as the first argument: this
becomes this
.
class User extends Matreshka { constructor() { super(); this.bindNode('fullName', '.full-name'); this.calc('fullName', ['firstName', 'lastName'], (firstName, lastName) => { return `${firstName} ${lastName}` }); // chained call can be used there: super().bindNode(...).calc(...); } }
This class combines the following classes Matreshka.Array
and Matreshka.Object
, declaring common methods for all classes.
The Matreshka.Object
class is responsible for data, such as "key-value". This is the data that the developer wants to separate from all other properties and, say, send to a server or save to local storage. To do this, the framework needs to indicate which properties are responsible for the business logic (for example, the user name), which for the temporary state of the application (for example, whether the DOM element is visible). The addDataKeys
method is responsible for addDataKeys
.
class User extends Matreshka.Object { constructor() { super(); this.firstName = 'Brendan'; this.lastName = 'Eich'; this.language = 'JavaScript'; this.addDataKeys(['firstName', 'lastName']); console.log(JSON.stringify(this)); // '{"firstName": "Brendan", "lastName": "Eich"}' } }
Matreshka.Array
- responsible for massive collection in the framework. Instances, besides the methods inherited by Matreshka
, contain methods from the native Array
( push
, splice
, map
, reduce
...).
For collections, like Backbone.Collection, you can specify a “model” ( Model
property) with the difference that it can pop out any class.
class Users extends Matreshka.Array { get Model() { return User; } constructor() { super(); this.push({ firstName: 'Brendan', lastName: 'Eich' }); console.log(this[0] instanceof User); // true } }
The collection can be automatically rendered when content changes. To do this, specify where to insert the newly created DOM elements and how to render them. To specify the rendering location, you need to bind the sandbox
property, or the container
property (see the documentation for the difference) DOM node. To specify how to render an item, you need to use the itemRenderer virtual method (or define a renderer
for the model).
class Foo extends Matreshka.Array { itemRenderer() { return '<div>Hello, world!</div>'; } constructor() { super(); this.bindNode('sandbox', '.sandbox-node'); this.push({}); // inserts <div>Hello, world!</div> to .sandbox-node } }
More about classes in the post "From simple to simple . "
All of the above can be imported as a CJS / ES2015 module:
import calc from 'matreshka/calc'; import MatreshkaArray from 'matreshka/array';
A separate matreshka-router library is responsible for the routing. It associates parts of the address with properties:
Matreshka.initRouter(object, '/a/b/c/'); object.a = 'foo'; object.b = 'bar'; object.c = 'baz'; // makes location.hash to be #!/foo/bar/baz/
Described in detail in the article .
Matreshka.js fills the gap between juna and seigneur, allowing you to write modular and extensible applications early on. All that is required is knowledge of the language and basic concepts.
Thanks to all.
Source: https://habr.com/ru/post/325480/