-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
587c81d
commit 46353cf
Showing
11 changed files
with
5,627 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
dist |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
{ | ||
"plugins": ["sonarjs"], | ||
"extends": ["plugin:sonarjs/recommended"], | ||
"rules": { | ||
"array-bracket-spacing": 2, | ||
"brace-style": 2, | ||
"comma-dangle": 2, | ||
"comma-spacing": 2, | ||
"comma-style": 2, | ||
"curly": 2, | ||
"dot-notation": 2, | ||
"eol-last": 2, | ||
"eqeqeq": ["error", "smart"], | ||
"key-spacing": 2, | ||
"keyword-spacing": 2, | ||
"new-cap": 0, | ||
"no-empty": [2, { "allowEmptyCatch": true }], | ||
"no-eval": 2, | ||
"no-implied-eval": 2, | ||
"no-mixed-spaces-and-tabs": 2, | ||
"no-multi-str": 2, | ||
"no-sequences": 2, | ||
"no-trailing-spaces": 2, | ||
"no-underscore-dangle": 2, | ||
"no-use-before-define": 2, | ||
"no-with": 2, | ||
"semi-spacing": 2, | ||
"space-before-blocks": 2, | ||
"space-before-function-paren": [ | ||
2, | ||
{ "anonymous": "always", "named": "never" } | ||
], | ||
"space-in-parens": 2, | ||
"space-infix-ops": 2, | ||
"space-unary-ops": 2, | ||
"vars-on-top": 2, | ||
"wrap-iife": 2, | ||
"semi": [2, "always"], | ||
"indent": [ | ||
2, | ||
2, | ||
{ | ||
"SwitchCase": 1 | ||
} | ||
], | ||
"valid-jsdoc": 2, | ||
"no-var": "error", | ||
"no-undef": 1, | ||
"no-multi-spaces": "error", | ||
"no-multiple-empty-lines": "error", | ||
"one-var": "off", | ||
"no-continue": "off", | ||
"no-new-func": "warn", | ||
"no-plusplus": "off", | ||
"no-console": "warn", | ||
"complexity": ["warn", 20], | ||
"sonarjs/cognitive-complexity": ["warn", 20], | ||
"max-lines-per-function": [ | ||
"warn", | ||
{ "max": 300, "skipBlankLines": true, "skipComments": true } | ||
], | ||
"max-len": ["error", 120], | ||
"max-statements-per-line": ["error", { "max": 3 }] | ||
}, | ||
"env": { | ||
"shared-node-browser": true, | ||
"browser": true, | ||
"node": true, | ||
"es6": true | ||
}, | ||
"parserOptions": { | ||
"ecmaVersion": 9, | ||
"sourceType": "module", | ||
"ecmaFeatures": { | ||
"jsx": true, | ||
"modules": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
language: node_js | ||
|
||
node_js: | ||
- stable | ||
|
||
install: | ||
- yarn | ||
|
||
script: | ||
- yarn test | ||
|
||
after_success: yarn coverage |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,254 @@ | ||
[](https://npmjs.org/package/dragonbinder "View this project on npm") | ||
[](https://travis-ci.org/Masquerade-Circus/dragonbinder) | ||
[](https://david-dm.org/masquerade-circus/dragonbinder) | ||
 | ||
 | ||
[](https://www.codacy.com/app/Masquerade-Circus/dragonbinder?utm_source=github.com&utm_medium=referral&utm_content=Masquerade-Circus/dragonbinder&utm_campaign=Badge_Grade) | ||
[](https://codeclimate.com/github/Masquerade-Circus/dragonbinder/maintainability) | ||
[](https://coveralls.io/github/Masquerade-Circus/dragonbinder?branch=master) | ||
[](https://github.com/masquerade-circus/dragonbinder/blob/master/LICENSE) | ||
|
||
# dragonbinder | ||
A tiny framework agnostic state managment library inspired by Vuex. | ||
A tiny, less than 1kb, framework agnostic, state managment library inspired by Vuex. | ||
|
||
## Table of Contents | ||
|
||
- [Install](#install) | ||
- [Features](#features) | ||
- [Use](#use) | ||
- [State](#state) | ||
- [Getters](#getters) | ||
- [Mutations](#mutations) | ||
- [Actions](#actions) | ||
- [Listeners](#listeners) | ||
- [Tests](#tests) | ||
- [Contributing](#contributing) | ||
- [Legal](#legal) | ||
|
||
## Install | ||
|
||
You can get this library as a [Node.js](https://nodejs.org/en/) module available through the [npm registry](https://www.npmjs.com/): | ||
|
||
```bash | ||
// With npm | ||
$ npm install dragonbinder | ||
// With yarn | ||
$ yarn add dragonbinder | ||
``` | ||
|
||
Or you can use it standalone in the browser with: | ||
`<script src="https://cdn.jsdelivr.net/npm/dragonbinder"></script>` | ||
|
||
## Features | ||
|
||
- [x] Immutable state. | ||
- [x] Getters. | ||
- [x] Mutations. | ||
- [x] Actions. | ||
- [x] Listeners. | ||
|
||
## Use | ||
|
||
```javascript | ||
const createStore = require('dragonbinder'); | ||
|
||
const store = createStore({ | ||
state: { | ||
count: 0 | ||
}, | ||
mutations: { | ||
increment(state) { | ||
state.count++ | ||
} | ||
} | ||
}); | ||
|
||
store.commit('increment'); | ||
console.log(store.state.count) // -> 1 | ||
``` | ||
|
||
### State | ||
Dragonbinder use Proxies to create a state as a "single source of truth" which cannot be changed unless you commit a mutation. | ||
This is, you cannot delete, modify or add a property directly. This allow us to keep track of all changes we made to the state. | ||
|
||
// If you don't provide a initial state by the state property, Dragonbinder will create one. | ||
|
||
```javascript | ||
const store = createStore({ | ||
state: { | ||
count: 0 | ||
}, | ||
mutations: { | ||
addProperty(state, value) { | ||
state.hello = 'world'; | ||
}, | ||
modifyProperty(state) { | ||
state.count++ | ||
}, | ||
removeProperty(state) { | ||
delete state.count; | ||
} | ||
} | ||
}); | ||
|
||
// This will throw errors | ||
store.state.hello = 'world'; | ||
store.state.count++; | ||
delete state.count; | ||
|
||
// This will work as expected | ||
store.commit('addProperty'); | ||
store.commit('modifyProperty'); | ||
store.commit('removeProperty'); | ||
``` | ||
|
||
### Getters | ||
As with Vue, with Dragonbinder you can create getters to create computed properties based on the state. | ||
This getters will receive the state as first argument and all oteher getters as second. | ||
|
||
```javascript | ||
const store = createStore({ | ||
state: { | ||
todos: [ | ||
{ | ||
content: 'First', | ||
completed: false | ||
}, | ||
{ | ||
content: 'Second', | ||
completed: true | ||
} | ||
] | ||
}, | ||
getters: { | ||
completed(state){ | ||
return state.todos.filter(item => item.completed); | ||
}, | ||
completedCount(state, getters){ | ||
return getters.completed.length; | ||
} | ||
} | ||
}); | ||
|
||
console.log(store.getters.completed); // -> { content: 'Second', completed: true } | ||
console.log(store.getters.length); // -> 1 | ||
``` | ||
|
||
### Mutations | ||
Mutations are the only way to change the state and you must consider the next points when designing mutations. | ||
|
||
- Following the Vuex pattern, mutations must be synchronous. | ||
- Note that with Dragonbinder the state is deep frozen using `Object.freeze` to prevent direct changes. So, when you are changing the state by using a mutation, you can add, modify or delete only the fist level properties, second level properties will be read only. | ||
- Unlike many other libraries you can pass any number of arguments to a mutation. | ||
|
||
```javascript | ||
const store = createStore({ | ||
state: { | ||
hello: { | ||
name: 'John Doe' | ||
} | ||
}, | ||
mutations: { | ||
changeNameError(state, payload){ | ||
state.hello.name = payload; | ||
}, | ||
changeNameOk(state, payload){ | ||
state.hello = {...state.hello, name: payload}; | ||
}, | ||
changeNameTo(state, ...args){ | ||
state.hello = {...state.hello, name: args.join(' ')}; | ||
} | ||
} | ||
}); | ||
|
||
// This will throw an assign to read only property error | ||
store.commit('changeNameError', 'Jane Doe'); | ||
|
||
// This will work as expected | ||
store.commit('changeNameOk', 'Jane Doe'); | ||
|
||
// You can pass any number of arguments as payload | ||
store.commit('changeNameTo', 'Jane', 'Doe'); | ||
``` | ||
### Actions | ||
If you need to handle async functions you must use actions. And actions will always return a promise as result of calling them. | ||
```javascript | ||
const store = createStore({ | ||
state: { | ||
count: 0 | ||
}, | ||
mutations: { | ||
increment(state) { | ||
state.count++ | ||
} | ||
}, | ||
actions: { | ||
increment(state){ | ||
return new Promise((resolve) => { | ||
setTimeout(() => { | ||
store.commit('increment'); | ||
resolve(); | ||
}, 1000); | ||
}) | ||
} | ||
} | ||
}); | ||
|
||
store.dispatch('increment').then(() => console.log(store.state.count)); // -> 1 after one second | ||
``` | ||
|
||
### Listeners | ||
You can register/unregister callbacks to listen for changes. | ||
The callback will receive the state as the first argument, the property that was changed as second, the new value as third and the old value as the last argument. | ||
|
||
```javascript | ||
const store = createStore({ | ||
state: { | ||
count: 0 | ||
}, | ||
mutations: { | ||
increment(state) { | ||
state.count++ | ||
} | ||
} | ||
}); | ||
|
||
// Subscribe a named method | ||
let namedListener = (state, prop, newVal, oldVal) => console.log(`The property ${prop} was changed from ${oldVal} to ${newVal}`); | ||
store.subscribe(namedListener); | ||
|
||
// Subscribe an anonymous method | ||
let unsubscribeAnon = store.subscribe(() => console.log('Anonymous method triggered')); | ||
|
||
// Committing increment will trigger the listeners | ||
store.commit('increment'); | ||
// $ The property count was changed from 0 to 1 | ||
// $ Anonymous method triggered | ||
|
||
// Unsubscribe a named method | ||
store.unsubscribe(namedListener); | ||
|
||
// Unsubscribe an anonyous method | ||
unsubscribeAnon(); | ||
|
||
// Committing increment will do nothing as the listeners are already unsubscribed | ||
store.commit('increment'); | ||
|
||
``` | ||
|
||
## Development, Build and Tests | ||
Use `yarn dev` to watch and compile the library on every change to it. | ||
Use `yarn build` to build the library. | ||
Use `yarn test` to run tests only once | ||
Use `yarn dev:test` to run the tests watching changes to library and tests. | ||
Use `yarn dev:test:nyc` to run the tests watching changes and get the test coverage at last. | ||
|
||
## Contributing | ||
|
||
- Use prettify and eslint to lint your code. | ||
- Add tests for any new or changed functionality. | ||
- Update the readme with an example if you add or change any functionality. | ||
|
||
## Legal | ||
|
||
Author: [Masquerade Circus](http://masquerade-circus.net). License [Apache-2.0](https://opensource.org/licenses/Apache-2.0) |
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Oops, something went wrong.
Oops, something went wrong.