A Passive View MVC framework for modern web applications.
npm install @rivierasolutions/state.jsAn HTML - javascript framework that re-introduces the MVC (Model-View-Controller) architectural pattern into modern web applications.
#### MVC
State.js follows the philosphy that even the simplest web application, like any other standalone application, features:
- a View (the HTML document),
- a Controller (the javascript code controlling the View)
- a Model (the javascript code defining content and processes relevant to the application).
State.js handles interactions between the View and the Controller. Interactions between the Controller and the Model
may be sufficiently handled by existing frameworks (e.g. Redux for complex cases) or custom javascript code (for simple cases),
and are thus out of scope of this framewrok.
#### The Passive View
State.js introduces the concept of a Passive View - A View defined only by it's layout and state, which specifically does not define any logic.
All logic that controls how the state or layout of the View is transformed throughout it's lifecycle is delegated to the Controller,
and should be decoupled and separate from the View.
This concept stands in stark contrast to leading web development frameworks like:
- React - Where the View's layout and state are tightly intertwined with the Controller's logic inside JSX components.
- Angular, Vue - Where View and Controller separation exists, however logic may easily leak into the View's layout
through even simple directive expressions like . Such leaks dramatically increase 0">
the complexity of the View - Controller Contract, and thus introduce tight coupling between the View and Controller.
#### The View State: a View - Controller Contract
State.js introduces the View State - A JSON object defined in the View, that serves as a Contract between the View and the Controller. Hence:
- The Controller may implement crucial transformations of the View's layout required by the web application exclusively by updating the View state.
- The View may define crucial endpoints for Controller interaction exclusively by defining the View State.
- A UI/UX designer is able to develop and test the View's layout without a coupled Controller by manually modifying the View State of a rendered View.
- A web developer is able to develop and test the Controller's logic without touching the View's layout, or by working on a stub View that defines an equivalent View State.
- Enables full decoupling of the View (HTML) from the Controller (javascript code) in web applications.
- Introduces the View State - a Contract between the View and the Controller, stored within the DOM tree as a JSON object.
- The View State is defined within the View (HTML), using sytnax similar to JSONpath queries in special HTML attribures or HTML elements.
- The Controller (javascript code) may retrieve and update the view state at any time.
- Supports web components
- Features a Visual Studio Code extension to automatically generate .d.ts Contracts for HTML files (Views)
for typescript and type-aware javascript Controller development.
- Features a Google Chrome extension to manually manipulate the View State of a HTML file (View) without
a coupled Controller for interactive, Controllerless View design (also compatible with MS Edge).
Install throught npm
``bash`
npm install @rivierasolutions/state.js
Or include the minified version directly in your View's layout:
`html`
1. Clone, install & serve (on port 5500)
`bash`
git clone https://github.com/rivierasolutions/state.js.git
cd state.js
npm install
npm run servehttp://localhost:5500/examples/helloWorld/index.html
2. Navigate to e.g. in your favourite browser.
Consider a basic View index.html...
`html`
Hello
And a coupled Controller index.controller.js...
`js
document.addEventListener('StateLoaded', () => {
document.state.listener('toggle', toggleSubheader);
document.state.update({
headerMessage: 'Hello World',
showSubheader: true,
subHeaderMessage: 'from state.js',
onToggleSubheader: { 'click': 'toggle' }
});
function toggleSubheader(event, context) {
document.state.update({ showSubheader: !document.state.current().showSubheader });
}
});
`state-content
Based on the path definitions (similar to JSONpath queries) defined in the special HTML attributes , state-if, state-if-not and state-listen,index.html
the initial View State of the View is defined as:`json`
{
"headerMessage": "Hello",
"showSubheader": false,
"subHeaderMessage": "",
"onToggleSubheader": {}
}StateLoaded
Once the View State is fully loaded and the event if dispatched from the window.document object,index.controller.js
the Controller may:document.state.current()
- retrieve the current View State by calling document.state.update(myNewState)
- update the View State by calling .
Notice how upon the first document.state.update(...) call, the View State is updated by the Controller to:`json`
{
"headerMessage": "Hello World",
"showSubheader": true,
"subHeaderMessage": "from state.js",
"onToggleSubheader": { "click": "toggleSubheader" }
}state-if="@.showSubheader"
State.js automatically propagates the updated View State across the View's layout.
In particular:
- The attribute adds or removes it's element from the DOM tree based on the truthiness of the showSubheader state field.state-content="@.subHeaderMessage"
- The attribute renders text content in it's parent element based on the value of the subHeaderMessage state field.state-listen="@.onToggleSubheader"
- The attribute attaches DOM event listeners to it's parent element based on the keys and values of the onToggleSubheader state field.
- The tag allows defining state- attributes on text blocks without wrapping them in any other HTML tag.
Renders the value of the state field at [path] as this DOM element's text content. [path]
The state field is initialized to a string containing this DOM element's text content.
#### Example:
`html`
Initial paragraph text
---
Removes this DOM element and it's subtree from the DOM if the state field at [path] evaluates to falsy. [path]
The state field at is initialized to false.
#### Example:
`html`
#### Remarks
When the state field at [path] evaluates to falsy, the DOM element and it's subtree is not deleted.
It is instead wrapped in a element at the same position in the DOM tree. [path]
Conversely, this placeholder element's content is unwrapped when evaluates to truthy.
---
Removes this DOM element and it's subtree from the DOM if the state field at [path] evaluates to truthy. [path]
The state field at is initialized to false.
#### Example:
`html
#### Remarks
When the state field at
[path] evaluates to truthy, the DOM element and it's subtree is not deleted.
It is instead wrapped in a element at the same position in the DOM tree.
Conversely, this placeholder element's content is unwrapped when [path] evaluates to falsy.---
$3
Renders this DOM element and it's subtree for each element of the array at the state field
[path].
If [path] is not an array but is truthy, it is treated as an array with 1 element.
The state field at [path] is initialized to an empty array [].#### Example:
`html
0
list item text here
`#### Remarks
When the View's layout is analyzed to load the initial View State, state.js will wrap any DOM elements
containing the
state-foreach attribute in a
tag at the same position in the DOM tree.
The DOM subtree of this placeholder element will subsequently be cloned after it for each element of the array at [path].Paths starting with
@. (of any state- attributes inside the state-foreach element's DOM subtree)
will be resolved relative to their respective array item, (i.e. their paths within the View State object will start at the array item).
To reference state fields beyond the array item, start the state- attribute's path with $.. This will always resolve the state attribute's path
relative to the root of the View State object regardless of scope (see state-scope="[path]" for more information).##### Example:
`html
Updated with document.state.currrent().myList[...].listItemText
(varies per item)
Updated with document.state.current().footer
(equal for all items)
`The special
$index field, containing the array item's current index will be appended to each array item of state field [path].---
$3
Attaches DOM Event listeners defined in the state field
[path] to this DOM element.
This provides any Controller attached to this View a way to interact with this element's input.#### Example:
`html
`
`javascript
document.state.listener({
'onClick': (event, context) => console.log(Clicked button: ${context.id}),
'onHover': (event, context) => console.log(Hovered over button: ${context.id})
});document.state.update({
onButtonEvents: {
'click': 'onClick',
'mouseover': 'onHover',
context: { id: 'My-Wonderfull-Button' }
}
});
`#### Remarks
The
[path] state field is assumed to be an Object containing keys defined as DOM event names,
and values defined as names of javascript functions that will be triggered on the respective DOM event.
All functions used in [path] field must first have their names declared using the [element].state.listener()
method (see [element].state.listener(nameOrDict, fn) for details).The special
context field of the [path] state field will be passed as the 2nd argument of the called
listener function (the 1st argument being the DOM event itself).Event listeners are added to DOM elements based on the keys and values in the
[path] state field Object using:
`javascript
DOMelement.addEventListener(key, getListenerByName(value));
`
If a key-value pair is removed, or a value is updated to a different function reference,
the previous event listener will be automatically removed by state.js.---
$3
Set's the value of attribute
[name] on this DOM element to the value of the state field at [path].
The state field at [path] is initialized the value of attribute [name] on this DOM element.#### Example:
`html
`#### Remarks
Some attributes, like the
value attribute for tags, define initial values for special DOM element properties,
which will subsequently be updated based on user interaction.
State.js automatically handles two-way updates between these properties and the respective View State fields
for the following HTML element - attribute pairs:
- and value:
-