nova-frontend
v0.6.28
Published
Nova is an alternative to all those gigantic front-end frameworks, that often do more than is necessary when it comes to building simple UIs. Pure Vanilla Javascript is performance-wise the best way to build your front-end in a SPA, but it can be hard to
Downloads
106
Maintainers
Readme
Nova
An alternative lightweight Front-End Library
What is Nova?
Nova is an alternative to all those gigantic front-end frameworks, that often do more than is necessary when it comes to building simple UIs. Pure Vanilla Javascript is performance-wise the best way to build your front-end in a SPA, but it can be hard to organize it properly and as the project grows, it might end up very messy. This is where Nova comes in, being a lightweight library packed with functionality for creating and structuring UIs more easily.
Features
Nova comes with most of the needed built-in features for handling a single-page application. Features like easily generating html, routing and state-management.
Nova is built solely on classes which are a perfect fit to handle context by storing the temporary data in a few places as possible. The topics that are necessary to understand are:
- Elements
- Components
- The Generator
- Groups
- State
- Router
API
Classes
Component
Kind: global class
new Component()
The component is a wrapper for Elements. A component is basically a block of Elements. The best way to create a Component is to use the Generator. You can also supply it with an array of Elements.
Example
import { Generator } from 'nova';
const generator = new Generator();
const header = generator.createTree(`
header className: 'header'
h1 className: 'header__title' innerText: 'Hello World!'
h2 className: 'header__subtitle' innerText: 'This is my site.'
end`)
header.render(); //header is the component
component.elements ⇒ ArrayOfElements
Kind: instance property of Component
component.setProps(propsObject) ⇒ void
Set props of a generated component using Generator. When generating the component, you need to put the value where you want to set the props as '{{whatever}}'. Then when supplying the propsObject to the setProps function, you set the value by { whatever: your-value }
Kind: instance method of Component
| Param | Type | | --- | --- | | propsObject | Object |
Example
const task = generator.createTree(`
article className: 'task' id: '{{id}}'
h2 className: 'task__title' innerText: '{{title}}'
p className: 'task__description' innerText: '{{description}}'
button className: 'task__remove-button' innerText: 'X'
end`)
task.setProps({
id: 1,
title: 'Buy Milk',
description: 'With chocolate taste',
})
component.setState(state) ⇒ void
A clever way to set state directly to elements properties using Generator. It works similarily to setProps but with some modifications. To fully understand how this function works, it's recommended to read the docs about State and Generator first. When generating the component like setProps, you need to put the value where you want to set the state as '{{workerName.whateverProp}}' note the DOT '.'.
When supplying the initial state to State, you should supply it as an object with the key name of the worker with the preferred value, f.e { whateverWorker: { whateverField: 'whateverValue' }} (See example below for clarification). The field is the name you supply when generating the elements (Check generator example below for clarification).
The worker needs to return an object with all fields specified in the generator, else it will replace the state with undefined. Everytime the worker returns the object with the specified field, it will automatically update the value you supplied in the generator.
This method is very useful when you want the state to be managed by the library instead of supplying custom functions to update the text.
NOTE: The following example below is not using the intended project structure used for state, and should preferable be splitted into different files, but it's just a simple demonstration of how setState works.
Kind: instance method of Component
| Param | Type | | --- | --- | | state | Object |
Example
const whateverWorker = (state, action) => {
switch (action.type) {
case 'WHATEVER_ACTION':
return { whateverText: state[action.field] + action.appendText };
default:
return state;
}
};
const initState = { whateverWorker: { whateverText: 'yo' } };
const workers = State.mergeWorkers({ whateverWorker });
const state = new State(workers, initState);
state.createAction('whateverAction', { type: 'WHATEVER_ACTION' });
const generator = new Generator();
const header = generator.createTree(`
header
div
h1 innerText: '{{whateverWorker.whateverText}}'
end`);
header.setState(state);
state.subscribe(header);
//Gets the div as in the order supplied to generator
header.elements[1].addEventListener('click', () => {
state.dispatch(state.getAction('whateverAction', { appendText: 'HELLO', field: 'whateverText' }));
})
header.render();
component.retrieve(input) ⇒ Element
A fluid function that returns the elements searched for in a component based on id, class or tag. It checks for # to find a id and in taht case returns the element directly. For tags and classes, it will always return an array of the found elements.
Kind: instance method of Component
| Param | Type | | --- | --- | | input | String |
Example
const header = generator.createTree(`
div id: 'hello'
h1 innerText: 'Yo!'
div
h2 innerText: 'Welcome.'
end`)
const divWithIdHello = header.retrieve('#hello')
const bothDivs = header.retrieve('div');
component.changeParent(newParent)
Changes the components grandparent to another element supplied.
Kind: instance method of Component
| Param | Type | | --- | --- | | newParent | Element |
Example
const generator = new Generator();
const aNewParent = new Element('article', root, {}, true);
const header = generator.createTree(`
div id: 'grandparent'
h1 innerText: 'Welcome!'
div
h2 innerText: 'To my page...'
div
div
end`)
header.changeParent(aNewParent);
header.render();
component.render()
Calls node.appendChild for every node inside the elements of the component.
Kind: instance method of Component
component.unrender()
Calls node.removeChild for every node inside the elements of the component.
Kind: instance method of Component
component.deleteByIndex(index)
Discards element inside component based on index, either from array supplied or the order from generator.createTree. Calling this before render has undefined behavior. Note that index 0 will remove the whole component.
Kind: instance method of Component
| Param | Type | | --- | --- | | index | Number |
component.deleteById(id)
Deletes element based on ID, doesn't need any #. Else the same applies to this function as deleteByIndex.
Kind: instance method of Component
| Param | Type | | --- | --- | | id | String |
Element
Kind: global class
- Element
- new Element(type, parent, elementObject, init)
- .node ⇒ node
- .type ⇒ type
- .parent ⇒ parent
- .value ⇒ value
- .id ⇒ id
- .text ⇒ text
- .html ⇒ html
- .siblings ⇒ nodeArray
- .updateNode(elementObject) ⇒ void
- .toggleNode() ⇒ void
- .addNode() ⇒ void
- .removeNode() ⇒ void
- .changeParent(newParentNode) ⇒ void
- .addEventListener(event, callback) ⇒ void
- .addStyle(property, css) ⇒ void
- .createComponent()
- .beforeSibling() ⇒ void
- .afterSibling() ⇒ void
- .after(reference) ⇒ void
- .before(reference) ⇒ void
new Element(type, parent, elementObject, init)
The fundamental building block in Nova is the Element, which most other things in the library are built upon. The element is just a shell of the normal javascript node but adds extra functionality and shorter syntax to access and manipulate a node.
Returns: void
| Param | Type | Description | | --- | --- | --- | | type | string | An htmlTag, for example 'div' or 'button'. | | parent | Element | node | The parent in the DOM you want the element to belong to. | | elementObject | object | An object containing the javascript props like: { innerText: 'helo' } | | init | boolean | A boolean to indicate if you want to render the element now (default: false). |
Example
import { Element, root } from 'nova';
const h1 = new Element('h1', root, { innerText: 'Hello World' }, true);
element.node ⇒ node
Return the node of the element
Kind: instance property of Element
Example
const h1 = new Element('h1', root, { innerText: 'Hello World' }, true);
console.log(h1.node) //returns node
element.type ⇒ type
Return the type of the element
Kind: instance property of Element
Example
const h1 = new Element('h1', root, { innerText: 'Hello World' }, true);
console.log(h1.type) //returns 'h1'
element.parent ⇒ parent
Return the parent of the element
Kind: instance property of Element
Example
const h1 = new Element('h1', root, { innerText: 'Hello World' }, true);
console.log(h1.parent) //returns node of root
element.value ⇒ value
Return the value of the element
Kind: instance property of Element
Example
const input = new Element('input', root, { type: 'text', value: 'some text' }, true);
console.log(h1.value) //returns 'some text'
element.id ⇒ id
Return the id of the element
Kind: instance property of Element
Example
const h1 = new Element('h1', root, { id: 'title', innerText: 'Welcome!' }, true);
console.log(h1.id) //returns 'title'
element.text ⇒ text
Return the text of the element
Kind: instance property of Element
Example
const h1 = new Element('h1', root, { id: 'title', innerText: 'Welcome!' }, true);
console.log(h1.text) //returns 'Welcome!'
element.html ⇒ html
Return the innerHTML of the element
Kind: instance property of Element
Example
const h1 = new Element('h1', root, { id: 'title', innerText: 'Welcome!' }, true);
console.log(h1.html) //returns '<h1 id="title">Welcome!</h1>'
element.siblings ⇒ nodeArray
Return the siblings of the element
Kind: instance property of Element
Example
const h1 = new Element('h1', root, { id: 'title', innerText: 'Welcome,' }, true);
const h2 = new Element('h2', root, { id: 'subtitle', innerText: 'To an awesome page!' }, true);
console.log(h1.siblings) //returns a nodeArray of h1 and h2.
element.updateNode(elementObject) ⇒ void
Dynamically updates the element by passing an object containing the props you want to update
Kind: instance method of Element
| Param | Type | Description | | --- | --- | --- | | elementObject | object | An object containing the javascript props like: { innerText: 'helo' } |
Example
const h1 = new Element('h1', root, { id: 'welcome', innerText: 'Hello World!' }, true);
h1.updateNode({ id: 'goodbye', innerText: 'Goodbye World...' })
element.toggleNode() ⇒ void
Toggles node on and off
Kind: instance method of Element
Example
const h1 = new Element('h1', root, { id: 'welcome', innerText: 'Hello World!' }, true); //On with true
h1.toggleNode() //Off
h1.toggleNode() //On
element.addNode() ⇒ void
Appends node to parent.
Kind: instance method of Element
Example
const h1 = new Element('h1', root, { id: 'welcome', innerText: 'Hello World!' }); //Off
h1.addNode() //On
element.removeNode() ⇒ void
Removes node from parent.
Kind: instance method of Element
Example
const h1 = new Element('h1', root, { id: 'welcome', innerText: 'Hello World!'}, true); //On
h1.removeNode() //Off
element.changeParent(newParentNode) ⇒ void
Appends node to new parent.
Kind: instance method of Element
| Param | Type | | --- | --- | | newParentNode | Element | node |
Example
const div = new Element('div', root, { className: 'container' }, true);
const h1 = new Element('h1', root, { id: 'welcome', innerText: 'Hello World!'}, true); //Appends to root
h1.changeParent(div); //Now h1 is appended to div instead.
element.addEventListener(event, callback) ⇒ void
Calls addEventListener on node.
Kind: instance method of Element
| Param | Type | Description | | --- | --- | --- | | event | event | //f.e 'click' Any javascript supported event. | | callback | function | Callback function to be invoked when event happens. |
Example
Check https://developer.mozilla.org/en-US/docs/Web/API/EventTarget/addEventListener
element.addStyle(property, css) ⇒ void
Dynamically adds css styles to Element.
Kind: instance method of Element
| Param | Type | Description | | --- | --- | --- | | property | string | A string containing what you want to change, for example 'color' | | css | string | The css value to use, for example 'red' |
element.createComponent()
Creates a component putting the element inside
Kind: instance method of Element
element.beforeSibling() ⇒ void
Moves node one step up the tree, changing place with it's previous sibling.
Kind: instance method of Element
element.afterSibling() ⇒ void
Moves node one step down the tree, changing place with it's next sibling.
Kind: instance method of Element
element.after(reference) ⇒ void
Appends node after a reference sibling
Kind: instance method of Element
| Param | Type | | --- | --- | | reference | Element |
element.before(reference) ⇒ void
Appends node before a reference sibling
Kind: instance method of Element
| Param | Type | | --- | --- | | reference | Element |
Generator
Kind: global class
new Generator()
The generator is a powerful way to generate HTML without writing actual HTML! It's meant to be very straightforward and to give your SPA a nice structure. No more angle brackets!
Example
import { Generator } from 'nova';
const generator = new Generator();
const header = generator.createTree(`
header className: 'header'
h1 className: 'header__title' innerText: 'Hello World!'
h2 className: 'header__subtitle' innerText: 'This is my site.'
end`)
header.render();
generator.createTree(input) ⇒ Component
'createTree' is the method you can use to generate HTML stored in a Component as Elements.
The string has to be in a certain format where indentations are very important.
Indentations are what dictates if an element is a parent/child. Always use even indentations, 2 spaces per child.
The structure is always the same: [indentation][htmlTag][property]: '[value]'
.
Note that you need to to use only one grandparent for all the element generated, else it will throw an error!.
like:
Kind: instance method of Generator
| Param | Type | | --- | --- | | input | string |
Example
` h1 innerText: 'helo'`
Valid properties are for specific a htmlTag. For example, you can use 'type' on 'input' but not on 'h1'
Example
` form
input type: 'text'
end`
Indentation dictates children/parent
Example
`
div className: 'grandparent'
main className: 'parent'
p className: 'child' innerText: 'I am a child of main'
end`
Always end the string on a new line with the word 'end'
Full example
Example
import { Generator } from 'nova';
const generator = new Generator();
const header = generator.createTree(`
header className: 'header'
h1 className: 'header__title' innerText: 'Hello World!'
h2 className: 'header__subtitle' innerText: 'This is my site.'
nav id: 'menu'
ul className: 'menu__items'
li innerText: 'First item'
li innerText: 'Second item'
end`)
Group
Kind: global class
new Group(arrayOfComponents, parent)
A group is a wrapper for components where it's possible to do bulk operations. This is very usefuly when you want a group of components to have the same parent. In a situation where you want to append new components, for example in a todo-list, it's highly recommended to wrap them all in a group so they share the same grandparent.
| Param | Type | Description | | --- | --- | --- | | arrayOfComponents | Array.<Components> | An array of components, every element will be automatically converted to a component. | | parent | Element | Element used to wrap the components, changing the grandparent node. |
Example
const taskOne = generator.createTree(`
...
`)
const taskTwo = ...
const taskWrapper = new Element('section', root, { className: 'task-wrapper' });
const tasks = new Group([taskOne, taskTwo], taskWrapper);
tasks.render();
group.components ⇒ Array.<Component>
Returns an array of components
Kind: instance property of Group
group.render()
Calls render on all Components inside Group. Same as Component.render().
Kind: instance method of Group
group.add(component)
Adds a new component to the group dynamically. This also has the effect of assigning the wrapper grandparent to the added component. Very useful when you are adding new components dynamically to the DOM, like more todos in a list.
Kind: instance method of Group
| Param | Type | | --- | --- | | component | Component |
group.update(arrayOfComponents)
Replaces the components in the group with a new array of components.
Kind: instance method of Group
| Param | Type | | --- | --- | | arrayOfComponents | Array.<Component> |
group.unrender()
Calls unrender on all components. Same as Component.unrender().
Kind: instance method of Group
group.retrieve(id) ⇒ Array.<elements>
Retrieve a component in a group. At the moment only supports ids.
Kind: instance method of Group
| Param | Type | | --- | --- | | id | String |
group.deleteById(id)
Deletes a component in a group by id.
Kind: instance method of Group
| Param | Type | | --- | --- | | id | String |
Router
Kind: global class
new Router(path, componentArray)
To get the full SPA feel the router is here for rendering different groups or components based on the url.
| Param | Type | Description | | --- | --- | --- | | path | String | The path bound to this route. All components applied will be rendered only when the URI is the same. | | componentArray | Array.<Components> | Array of components. If you want to supply an element you can do Element.createComponent(); |
Example
import { Router, Group, Element, Generator, root } from '../../';
const wrapper = new Element('div', root, { id: 'wrapper' }, true);
const generator = new Generator;
const component = generator.createTree(`
div
h1 innerText: 'Router example.'
button innerText: 'Click it'
end`)
component.retrieve('button')[0].addEventListener('click', () => {
Router.changePath('/about');
})
const about = new Element('h1', root, { innerText: 'Hello there!'}).createComponent();
const group = new Group([component], wrapper);
new Router('/', [group])
new Router('/about', [about]);
Router.getPath() ⇒
Returns current url path
Kind: static method of Router
Returns: current path
Router.changePath(newPath)
A static mathod that uses history.pushState to set new url location.
Kind: static method of Router
| Param | Type | | --- | --- | | newPath | String |
Example
Router.newPath('/contact');
State
Kind: global class
- State
- new State()
- instance
- .getState() ⇒ Object
- .createAction(name, deps)
- .getAction(name, deps) ⇒ Objectt
- .subscribe(listener) ⇒ function
- .dispatch(action)
- static
- .mergeWorkers(workers) ⇒ Object
new State()
State management system for Nova. Heavily inspired by Redux with a similar system but in a way, more compact.
Example
const whateverWorker = (state, action) => {
switch (action.type) {
case 'WHATEVER_ACTION':
return { whateverText: state[action.field] + action.appendText };
default:
return state;
}
};
const initState = { whateverWorker: { whateverText: 'yo' } };
const workers = State.mergeWorkers({ whateverWorker });
const state = new State(workers, initState);
state.createAction('whateverAction', { type: 'WHATEVER_ACTION' });
const generator = new Generator();
const header = generator.createTree(`
header
div
h1 innerText: '{{whateverWorker.whateverText}}'
end`);
header.setState(state);
state.subscribe(header);
//Gets the div as in the order supplied to generator
header.elements[1].addEventListener('click', () => {
state.dispatch(state.getAction('whateverAction', { appendText: 'HELLO', field: 'whateverText' }));
})
state.getState() ⇒ Object
Returns the state object.
Kind: instance method of State
Returns: Object - state object
Example
const initState = { whateverWorker: { title: 'Yo!', desc: 'Hello there...' } };
const state = new State(workers, init);
const whateverWorkerState = state.getState().whateverWorker;
state.createAction(name, deps)
Creates the action for the worker, which you can access from the action argument in the callback. The name supplied as the first argument needs to be unique for every action.
Kind: instance method of State
| Param | Type | Description | | --- | --- | --- | | name | String | unique name for action. | | deps | Object | object containing prop "type". |
Example
state.createAction('actionName', { type: 'ACTION' })
state.getAction(name, deps) ⇒ Objectt
This function is used to set new dependencies to a specific action. Prefarably called together with dispatch as the argument. The dependency can contain an optional property for use when setting up state together with "setState". See Component for more info regarding the optional property.
Kind: instance method of State
Returns: Objectt - - returns the dependencies.
| Param | Type | Description | | --- | --- | --- | | name | String | the name of the action. | | deps | Object | the dependencies that will be accessible through the action argument in the worker. |
Example
state.dispatch(state.getAction('actionName', { someText: 'helo', optionalProperty: 'title' }));
state.subscribe(listener) ⇒ function
The subscribe function takes 3 different listeners as an argument. If you use "setState" together with a component, you will supply the component as the argument. Generelly when not using "setState", you want to supply an object with the action type and function. This will make sure that the function only gets called when the specific action is set, see example. If you supply a function directly, that one will get called every time you use dispatch, which is generelly unnecessary.
Kind: instance method of State
Returns: function - . unsubscribe function, call it to remove listener.
| Param | Type | | --- | --- | | listener | Component | Object | function |
Example
state.subscribe(header); //Component
state.subscribe({ type: 'TASK_ADD', func: addTask }); //Only called when "TASK_ADD" is dispatched.
state.subscribe(addTask); //Called every dispatch.
state.dispatch(action)
Dispatch is what you call to update state. It's preferable to call it together with "getAction". It first calls the worker to get the state and modifications. Then it will call the listener you supplied with subscribe. How it will call the listener depends on what type of listener you called subscribe with.
Kind: instance method of State
| Param | Type | Description | | --- | --- | --- | | action | Object | the dependencies supplied, see "getAction". |
Example
state.dispatch(state.getAction('actionName', { someText: 'helo', optionalProperty: 'title' }));
State.mergeWorkers(workers) ⇒ Object
MergeWorkers is a static method that should always be used before supplying workers to state initialization.
Kind: static method of State
Returns: Object - state object
| Param | Type | Description | | --- | --- | --- | | workers | Object | takes and object that have the function as a key-value pair with same name |
Example
const exampleWorkerOne = (state, action) => {
switch(action.type) {
case 'ACTION':
return state.someText + 'hello again!';
default:
return state;
}
}
const exampleWorkerTwo = (state, action) => {
...
}
const initState = { exampleWorkerOne: { someText: 'hello, '}, exampleWorkerTwo: ... }
const workers = State.mergeWorkers({ exampleWorkerOne, exampleWorkerTwo });
const state = new State(workers, initState);