Skip to content

Commit

Permalink
Merge pull request #28 from fynyky/2.1
Browse files Browse the repository at this point in the history
2.1
  • Loading branch information
fynyky authored Jul 28, 2023
2 parents 3d9a42a + 4f30885 commit 15cb29a
Show file tree
Hide file tree
Showing 5 changed files with 269 additions and 131 deletions.
169 changes: 146 additions & 23 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,32 +1,58 @@
Reactor.js
==========

Reactor.js is a lightweight library for [reactive programming](http://en.wikipedia.org/wiki/Reactive_programming). It provides observer functions that automatically track the reactive variables that they use and get retriggered if any of these variables are updated. This makes it easy to keep a complex data model consistent, or a user interface up to date when a model is changed.
Reactor.js is a simple reactive front-end library. It provides
- `Reactor` objects that store reactive variables
- `Observer` functions that automatically track the reactive variables that they use and retrigger if any of these variables are updated. The function `ob` is shorthand for `new Observer`
- A function `el` that allows declarative element creation in javascript

Here's a quick example of what Reactor does:
```javascript
const reactor = new Reactor()
reactor.foo = 'bar'
const observer = new Observer(() => {
console.log('foo is ', reactor.foo)
import { Reactor, ob, el } from 'reactorjs'

const rx = new Reactor({
name: 'Anakin'
})
observer() // prints "foo is bar"
reactor.foo = 'moo' // prints "foo is moo"

document.body.appendChild(
el('main',
el('h1', 'Hello World!'),
el('h2', (x) => { x.id ='foo' }, () => 'returned text')
el('defaults to div', ['this', 'is', 'an', 'array'])
el('p more class names', ob(() => ('My name is ' + rx.name)))
)
)
// <main class="main">
// <h1 class="h1">Hello World!</h1>
// <h2 class="h2" id="foo">returned text</h2>
// <div class="defaults to div">thisisanarray</div>
// <p class="p more class names">My name is Anakin</p>
// </main>

rx.name = 'Darth'
// <p class="p more class names">My name is Anakin</p>
// Changes to
// <p class="p more class names">My name is Darth</p>
```
- Reactors work like normal objects that you can set and get properties on
- Observers work like normal functions that you can define and call
- When an observer reads a reactor it registers itself as a dependent
- When a reactor is updated it automatically retriggers its dependents
- `Reactor` objects work like normal objects that you can set and get properties on
- `Observer` functions work like normal functions that you can define and call
- When an `Observer` reads a `Reactor` it registers itself as a dependent
- When a `Reactor` is updated it automatically retriggers the dependent `Observer` functions

- `el` creates a DOM element
- The first argument is the type of element it creates
- Subsequent arguments are appended as children
- Function children are run with the context of the parent element
- Function return values are appended as children
- `Observer` functions automatically replace their child nodes when retriggered

Reactor.js is designed to be unobtrusive and unopinionated.
- No special syntax to learn. Everything is just plain javascript
- There is no need to manually declare listeners or bindings. Reactor automatically keeps track of all that for you.
- It imposes no particular structure on your code. Any variable can be easily replaced with a reactive one.
- There is no need to learn special syntax or a domain specific language. Reactors behave just like normal objects and you can observe any synchronous code.

If you want to see Reactor.js in action, take a look at this [example todo list](https://jsfiddle.net/uy0v16za/7)
- Use it for the whole front-end or just a few components. Elements created by Reactor are just normal DOM elements, and any variable can be easily replaced with a reactive one without changing the rest of your codebase.

Installation
------------------
------------

Reactor.js is [available on npm](https://npmjs.org/package/reactorjs). Install it by running:
```
Expand All @@ -35,15 +61,116 @@ $ npm install reactorjs

Import it using:
```javascript
import { Reactor, Observer, hide, batch, shuck } from 'reactorjs'
import {
el,
ob,
attr,
bind,
Reactor,
Observer,
hide,
batch,
shuck
} from 'reactorjs'
```

Summary
Summary
-------

```javascript
import { Reactor, Observer, hide, batch, shuck } from 'reactorjs'
import {
el, attr, bind, ob,
Reactor, Observer, hide, batch, shuck
} from 'reactorjs'

// el(description, children...)
el('h1') // Creates a h1 element with a class "h1"

el('notAValidTag') // Creates a div with class "notAValidTag"
// Anything not a valid html tag defaults to a div

el('notATag header body h1') // Creates a div with classes "notATag header body h1"
// Only the first word is used for tag type
// Subsequent words are just added as classes

el('.foo') // Strings starting with '.' or '#' are parsed as query selectors
el('#foo') // They try to find a matching element instead of making a new one

let aDiv = document.createElement('div')
el(aDiv) // Uses the provided element instead of creating a new one


el('h1', 'foo') // Creates <h1 class="h1">foo</h1>
// Strings provided as children are inserted as text nodes

el('h1', aDiv) // Creates <h1 class="h1"><div></div></h1>
// Elements provided as children are just appended

el('h1', function(){this.id = 'foo'}) // Creates <h1 class="h1" id="foo"></h1>
// Functions provided as children are
// executed in the context of the parent

el('h1', x => { x.id = 'foo' }) // Also creates <h1 class="h1" id="foo"></h1>
// The parent is also provided as an argument
// This allows arrow functions to work

el('h1', () => "return value") // Creates <h1 class="h1">return value</h1>
// Return values are appended as children

let aPromise = new Promise()
el('h1', aPromise) // Creates <h1 class="h1"><!-- promisePlaceholder --></h1>
// Places a comment to be replaced when the promise resolves
aPromise.resolve('resolved!') // Becomes <h1 class="h1">resolved!</h1>


// Example of how el works with reactors and observers
// Full explanation of how Observers and Reactors work comes later on
// Attached observers use comments to bookmark their children
let rx = new Reactor({ foo: 'foo' })
let reactiveEl = el('h1', ob(() => rx.foo))
// Creates
// <h1 class="h1">
// <!-- observerStart -->
// foo
// <!-- observerEnd -->
// </h1>

document.body.appendChild(reactiveEl) // Attached observers sleep when their
// parent is out of the DOM
// Need to attach it for reactivity

// When updated anything inbetween the bookmarks gets replaces
rx.foo = 'bar'
// Updates to
// <h1 class="h1">
// <!-- observerStart -->
// bar
// <!-- observerEnd -->
// </h1>


el('h1', ['foo', 'bar', 'qux']) // Creates <h1 class="h1">foobarqux</h1>
// Arrays or any iterable are done recursively

// attr is shorthand for setting attributes
// These 2 are equivalent
el('h1', attr('id', 'foo'))
el('h1', self => self.setAttribute('id', 'foo'))

// bind is shorthand for 2 way binding with a reactor
// These 2 are equivalent
el('h1', bind(rx, 'foo'))
el('h1', self => {
self.oninput = () => { rx['foo'] = self.value }
return new Observer(() => { self.value = rx['foo'] })
})

// ob is shorthand for creating Observers
// These 2 are equivalent
ob(function(){})
new Observer(function(){})

// Reactors and Observers
const reactor = new Reactor({ foo: 'bar' })
const observer = new Observer(() => {
const result = 'reactor.foo is ' + reactor.foo // Sets a dependency on foo
Expand Down Expand Up @@ -496,7 +623,3 @@ Comparison to Other Libraries
-----------------------------

Reactor is based on the same reactive principles as [Bacon.js](https://github.com/raimohanska/bacon.js) and [Knockout.js](http://knockoutjs.com/). The main difference is that Reactor is trying to be lightweight and keep the additional syntax and complexity to a minimum. Reactor sets dependencies for you automatically so there is no need to manually set subscriptions/listeners.

Compared to Knockout, Reactor does not provide semantic bindings directly to HTML. Instead, users set the appropriate HTML modifying functions as Observers.

Compared to Bacon, Reactor does not help to handle event streams.
4 changes: 2 additions & 2 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

14 changes: 9 additions & 5 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "reactorjs",
"version": "2.1.0",
"version": "2.1.1",
"description": "Simple reactive programming without frameworks",
"source": "src/index.js",
"main": "dist/index.js",
Expand All @@ -11,18 +11,22 @@
"optimize": true
}
},
"files": ["dist/index.*"],
"files": [
"dist/index.*"
],
"devDependencies": {
"mocha": "^10.1.0",
"parcel": "^2.8.1",
"standard": "^17.0.0"
},
"scripts": {
"lint": "standard --fix",
"build": "parcel build",
"build": "rm -rf dist/ && parcel build",
"prepare": "npm run build",
"pretest": "npm run build",
"test": "standard && mocha test/reactor.test.js",
"browserTest": "parcel serve test/elementary.test.html",
"prepublish": "npm test"
"browserTest": "npm run pretest && parcel serve test/elementary.test.html",
"prepublishOnly": "npm test"
},
"repository": {
"type": "git",
Expand Down
Loading

0 comments on commit 15cb29a

Please sign in to comment.