[dey-uh s jey-uh s]
A very lightweight front-end framework inspired by React but easier to learn and use.
Warning: This package is new and under development.
If you know HTML and JavaScript, you can learn DeusJS very quickly.
Get your hands on deusjs.mjs and save it in your project.
DeusJS is available on NPM: npm install deusjs -S
or with Yarn (recommended): yarn add deusjs
import MyApp from './deusjs.mjs';
If you're using an environment where CommonJS modules are supported in lieu of ES modules, you'll need deusjs.cjs instead.
const MyApp = require('./deusjs.cjs');
index.html
:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<h1>Loading...</h1>
<script type="module" src="./helloworld.js"></script>
</body>
</html>
helloworld.js
:
import helloWorldApp from "./deujs.mjs";
class HelloWorldScreen extends helloWorldApp.Cmp {
title = 'Hello World App';
state = {
helloList: [],
};
addHello() {
var helloList = this.state.helloList;
helloList.push('hello');
this.set({'helloList': helloList});
}
html() {
return `
<h1>hello, world!</h1>
<p>This is the HelloWorldApp.</p>
<button id="hello-button">say hello</button>
<div>${this.use(ListMaker, {items: this.state.helloList})}</div>
`;
}
post() {
this.e.getElementById("hello-button").addEventListener('click', this.addHello);
}
}
class ListMaker extends helloWorldApp.Cmp {
html() {
var items = this.props.items;
return `
<ul>
${items.map(item => `<li>${item}</li>`).join('')}
</ul>
<style>
ul {
color: green;
}
</style>
`;
}
}
helloWorldApp.go(HelloWorldScreen);
This component gets the contents of the paragraph passed to it as a prop from its parent.
html() {
return `
<p>${this.props.paragraphText}</p>
`;
}
This JavaScript placeholder has an expression that will print out the number 4
.
html() {
return `
<p>2 + 2 = ${2 + 2}</p>
`;
}
This is how the sausage is made. You'll be doing this a lot. In this example a component called "SampleText" is inserted.
html() {
return `
<p>${this.use(SampleText)}</p>
`;
}
this.use(component[, props])
Rerendering can only be done through setting state by calling this.set({someKey: someVal})
.
If you want to rerender, then something changed, and you need to represent
that change by tracking a state for something. That being said, calling
this.set({})
without changing the value of the state allows you to
trigger a rerender if there is some external factor (such as global state)
that has caused the return value of html() to change.
this.set({key1: val1[, key2: val2, ...]})
Working with event attributes like onClick
has limited use with HTML strings
and template literals, therefore adding event listeners in the post()
function is the way to go. There are other benefits to using this style of
event handling, and you may find it preferable anyway.
The long form, which you can modify easily, is:
post() {
this.e.querySelectorAll('div#query button.selector')[0].addEventListener('click', this.makeClick);
}
You'll probably need this quite a bit too, so there's some shorthand:
post() {
this.$('div#query button.selector', 'click', this.makeClick);
}
Here are all the member functions you can implement.
load()
Called when the component is loaded.html()
Return the HTML template of your component, called prior to every potential rerender.post()
Called after the HTML is rendered.unload()
Called when the component is about to unload.
Some other things you can access:
this.props
The props passed in from a parent'sthis.use()
or viaMyApp.go()
.this.state
The state object resulting from your calls tothis.set()
.this.c
Array of children, each in the format of an object with keysn
(name),p
(props), andc
(component).this.p
Parent component (will be undefined for the root component).this.e
The container element where this component's output is being placed.this.r
An internally used rendering flag, modifying this may lead to undefined behaviour.this.i
An internally used component ID.
You can manipulate parent and child components, e.g.
this.p.set({someKey: someValue})
, this circumvents the classic reactive workflow.
DeusJS needs no special facility for styles. Create global CSS for your
app in your root HTML document. Component related CSS can be supplied in
the HTML via <style></style>
tags, inline styles (via the HTML 'style'
attribute), and any manipulation via JavaScript that is needed.
Basic navigation is demonstrated above with the HelloWorld example.
MyApp.go(component[, props, method, container])
MyApp.back([numberOfSteps, ..?])
Screen titles are set by the component's title
property.
You don't need to configure any navigation paths, the navigation system will just load and render components as needed, and the URL will be appended with the component's name. However you can do this if you want to:
// Register it.
myApp.r['some/uri/here'] = MyComponentClass;
// Use it.
myApp.go('some/uri/here');
These can be dynamically added and deleted at runtime as required.
By default components are updated using a DOM diff that makes minimal updates to the DOM. This may not be always desirable as it doesn't allow for transition animations. A component's default attachment method can be set like so:
module.exports = class Example extends MyApp.Cmp {
m: 2000,
html() {
return "Lorem Ipsum Dolor Sit Amet";
}
}
In this case the component class property m
is set to 2000. The "new" element
will be appended to the container as a sibling to the "old" element. The "old"
element will receive a new class name rm
. After 2000ms the "old" element will
be removed from the document. It is advisable to use CSS to transition out the
"old" element based on the .rm
selector. Your "new" element could start
invisible or off-screen, and transition into view. Ensure your CSS transitions
are done within the time supplied.
To restore default behaviour:
delete this.m;
You can supply a custom DOM attachment function like so:
module.exports = class Example extends MyApp.Cmp {
m: (element, containerElement) => {
// Your DOM manipulation code here.
},
html() {
return "Lorem Ipsum Dolor Sit Amet";
}
}
You can override this when navigating by setting the method
arg of myApp.go()
using the delay integer, or the callback. Note: the callback also receives a
3rd argument; the component itself.
Components can communicate and pass data via the event emitter and listeners.
Components typically use MyApp.on()
in their load()
function, and MyApp.off()
in their unload()
function. Alternatively a one-time listener can be
created anywhere with MyApp.once()
.
MyApp.on(eventName[, callback])
MyApp.off(eventName[, callback])
MyApp.once(eventName[, callback])
The callback will receive a single argument which is the data sent by the
emitter. And yes, you need to supply the exact same callback to the .off()
function as you did in the on()
function so it can identify which listener to remove.
Components emit an event that another is listening for:
MyApp.emit(eventName[, data])
Setting global states doesn't trigger any rerender unless you also call
this.set({})
within the component that should be rechecked.
MyApp.set({key1: val1[, key2: val2, ...]})
var value = MyApp.state.key1