prepared by Vladimir Cores
Benefits
Vue.js is focused on the ViewModel layer of the MVVM pattern.
It connects the View and the Model via two-way data bindings.
- beforeCreate
- created
- beforeMount
- mounted
- beforeUpdate
- updated
- beforeDestroy
- destroyed
SingleFileComponent.vue
<template> (HTML or PUG)
- directives (and ref)
- bindings (mustache)
- pipe filters
<script>
- components
- data, props
- computed (get, set)
- methods
- watch
<style> (SCSS or LESS)
- scoped
Separation of Concerns
<!-- my-component.vue -->
<template>
<div>This will be pre-compiled</div>
</template>
<script src="./my-component.js"></script>
<style src="./my-component.css"></style>
Vue performs DOM updates asynchronously.
Whenever a data change is observed, it will open a queue and buffer all the data changes that happen in the same event loop. If the same watcher is triggered multiple times, it will be pushed into the queue only once.
This buffered de-duplication is important in avoiding unnecessary calculations and DOM manipulations. Then, in the next event loop “tick”, Vue flushes the queue and performs the actual (already de-duped) work.
<p v-once>Using mustaches: {{ message }}</p>
<p>Reversed message: "{{ message && reverseMessage() }}"</p>
// in component methods: { reverseMessage: function () { return this.message.split('').reverse().join('') } }
Mustaches cannot be used inside HTML attributes. Instead, use a v-bind directive:
<button v-bind:disabled="isButtonDisabled">Button</button>
Directive attribute values are expected to be a single JavaScript expression
A directive’s job is to reactively apply side effects to the DOM when the value of its expression changes
special postfixes denoted by a dot
data: {
isActive: false,
hasError: true,
activeClass: 'active',
errorClass: 'text-danger',
styleObject: {
color: 'red',
fontSize: '13px'
}
},
computed: {
classObject: function () {
return {
active: this.isActive && !this.error,
'text-danger': this.error && this.error.type === 'fatal'
}
}
}
1
1
2
2
3
4
4
v-if is “real” conditional rendering because it ensures that event listeners and child components inside the conditional block are properly destroyed and re-created during toggles.
<h1 v-show="ok">Display ON/OFF</h1>
v-show only toggles the display CSS property of the element.
Array: <li v-for="(item, index) in items"> <my-comp v-for="item in items" :key="item.id"/>
Object: <div v-for="(value, key, index) in object"> {{ index }}. {{ key }}: {{ value }} </div>
Range: <span v-for="n in 10">{{ n }} </span>
In 2.2.0+, when using v-for with a component, a key is now required.
parent-child component relationship can be summarized as props down, events up.
The parent passes data down to the child via props, and the child sends messages to the parent via events.
const AsyncComp = () => ({
// The component to load. Should be a Promise
component: import('./MyComp.vue'),
// A component to use while the async component is loading
loading: LoadingComp,
// A component to use if the load fails
error: ErrorComp,
// Delay before showing the loading component. Default: 200ms.
delay: 200,
// The error component will be displayed if a timeout is
// provided and exceeded. Default: Infinity.
timeout: 3000
})
todo: { text: 'Learn Vue', isComplete: false }
<todo-item v-bind="todo"></todo-item>
<child :message="dataFromParent"></child>
use v-bind for dynamically binding props to data on the parent. Whenever the data is updated in the parent, it will also flow down to the child.
All props form a one-way-down binding
props: { propA: Number, // basic type check (`null` means accept any type) propB: [String, Number], // multiple possible types propC: { // a required string type: String, required: true }, propD: { // a number with default value type: Number, default: 100 }, propE: { // object/array defaults should be returned from a factory function type: Object, default: function () { return { message: 'hello' } } }, propF: { // custom validator function validator: function (value) { return value > 10 } } }
- String
- Number
- Boolean
- Function
- Object
- Array
- Symbol
Every Vue instance implements an events interface:
<message @event="onSomething"/>
Vue.component('message', {
template: `<div>
<input type="text" v-model="value" />
<button v-on:click="handleSendMessage">Send</button>
</div>`,
data: function () { return { value: 'inner value' } },
methods: {
handleSendMessage: function () {
this.$emit('event', { ...this.$data })
}
}
})
Methods to be mixed into the Vue instance.
<template>
<div class="gallery-view" v-if="ready">
<GalleryViewItem
v-for="item in items"
:onSelected="OnGalleryItemSelected"
:isSelected="isItemSelected(item)"
/>
</div>
</template>
export default
{
name: 'GalleryView',
components: {
GalleryViewItem
},
props: ['selectedItem'],
methods: {
OnGalleryItemSelected (index) { this.$emit(EVENT_SELECT, index) },
isItemSelected (item) {
return this.selectedItem && this.selectedItem.index === item.index
}
},
computed: {
...mapGetters({
ready: IS_GALLERY_READY,
items: GET_GALLERY_VIEW_ITEMS
})
}
}
var vm = new Vue({ data: { a: 1 }, methods: { plus: function () { this.a++ } } }) vm.plus() vm.a // 2
Computed properties are cached based on their dependencies.
A computed property will only re-evaluate when some of its dependencies have changed.
<div id="example">
<p>Original message: "{{ message }}"</p>
<p>Computed reversed message: "{{ reversedMessage }}"</p>
</div>
data: {
message: 'Hello'
},
computed: {
// a computed getter
reversedMessage: function () {
return this.message.split('').reverse().join('')
}
}
This means as long as message has not changed, multiple access to the reversedMessage computed property will immediately return the previously computed result without having to run the function again.
Computed properties are by default getter-only
computed: {
fullName: {
// getter
get: function () {
return this.firstName + ' ' + this.lastName
},
// setter
set: function (newValue) {
var names = newValue.split(' ')
this.firstName = names[0]
this.lastName = names[names.length - 1]
}
}
}
<div id="component">
<user-profile ref="profile"/>
</div>
{ // somewhere in a code
var profile = this.$refs.profile
}
$refs are only populated after the component has been rendered, and it is not reactive.
- SLOTS
- MIXINS
- WATCHERS
- TRANSITIONS
- CUSTOM DIRECTIVES
- RENDER FUNCTIONS & JSX
- PLUGINS
- FILTERS
Vuex is a state management pattern / library.
Serves as a centralized store for all the components in an application, with rules ensuring that the state can only be mutated in a predictable fashion.
multiple components can share common state
Multiple components that share common state:
We extract the shared state out of the components, and manage it in a global singleton, any component can access the state or trigger actions
Vuex is also a library implementation tailored specifically for Vue.js to take advantage of its granular reactivity system for efficient updates.
It comes with the cost of more concepts and boilerplate. It's a trade-off between short term and long term productivity.
- State
- Getters
- Actions
- Mutations
- Modules
link : vuejs-tips.github.io/vuex-cheatsheet/
State
Single state tree - single object contains all your application level state
and serves as the "single source of truth".
export default new Vuex.Store({
state: new ApplicationVO(),
actions: {...},
getters: {...},
mutations: {...}
})
export default class ApplicationVO {
constructor () {
this.device = null
this.server = null
this.isReady = false
this.logged = false
}
}
<template>
<div id="app" v-if="isReady">{{ content }}</div>
<div v-else>Loading</div>
</template>
<script>
import ApplicationStore from '@/model/stores/ApplicationStore'
import { mapState } from 'vuex'
export default {
name: 'App',
store: ApplicationStore,
computed: {
...mapState(['isReady'])
},
data () {
return {
content: 'Inner State Variable'
}
}
}
</script>
mapState helper which generates computed getter functions
Getters
Compute derived state based on store state
const store = new Vuex.Store({
state: {
todos: [
{ id: 1, text: '...', done: true },
{ id: 2, text: '...', done: false }
]
},
getters: {
doneTodos: state => {
return state.todos.filter(todo => todo.done)
},
doneTodosCount: (state, getters) => {
return getters.doneTodos.length
}
}
})
<script>
import { mapGetters } from 'vuex'
...
computed: {
doneTodosCount () {
return this.$store.getters.doneTodosCount
},
...mapGetters([ 'doneTodos' ])
}
...
</script>
The mapGetters maps store getters to local computed properties
...mapGetters({
doneTodos: 'doneTodos',
doneCount: 'doneTodosCount'
})
Getters
Avoid using hardcoded Strings - in STORE
import GalleryGetter from '@/consts/getters/GalleryGetter'
import {
GALLERY_STORE_NAME
} from '@/consts/StoreNames'
let _PRIVATE_GET_USER_SETTINGS = 'private_getter_get_user_settings'
let _PRIVATE_GET_SERVER = 'private_getter_get_server'
const store = new Vuex.Store({
name: GALLERY_STORE_NAME,
state: new GalleryVO(),
getters: {
[GalleryGetter.IS_GALLERY_REGISTERED]: (state, getters, rootState) => { return rootState.hasOwnProperty(GALLERY_STORE_NAME) },
[GalleryGetter.IS_GALLERY_READY]: state => { ... },
[GalleryGetter.GET_GALLERY_VIEW_ITEMS]: state => { return (state && state.view != null) ? state.view.items : null },
[GalleryGetter.GET_GALLERY_VIEW_INDEX]: state => { return !!state && state.index ? state.index : 0 },
[_PRIVATE_GET_USER_SETTINGS]: (state, getters, root) => { return root.user ? root.user.settings : null },
[_PRIVATE_GET_SERVER]: (state, getters, root) => { return root.server }
}
})
Getters
Avoid using hardcoded Strings - in COMPONENT
<script>
import {
GALLERY_STORE_NAME
} from '@/consts/StoreNames'
import GalleryGetter from '@/consts/getters/GalleryGetter'
import { createNamespacedHelpers, mapState } from 'vuex'
const GALLERY_STORE_UTILS = createNamespacedHelpers(GALLERY_STORE_NAME)
const galleryMapState = GALLERY_STORE_UTILS.mapState
const galleryMapGetters = GALLERY_STORE_UTILS.mapGetters
export default {
computed: {
...galleryMapState(['selectedItem']),
...galleryMapGetters({ isGalleryReady: GalleryGetter.IS_GALLERY_READY })
},
}
</script>
Actions
Is simple "triggers" to process data from the view and mutate the state.
Usage maybe:
- calling an async API
- database interaction
- committing multiple mutations
Actions
STORE
import UserAction from '@/consts/actions/UserAction'
const UserStore = {
name: USER_STORE_NAME,
state: {},
actions: {
[UserAction.SIGNUP]: (store, payload) => { ... },
[UserAction.LOGIN]: (store, payload) => {
return LoginUserCommand.execute(payload.name, payload.password).then((result) => {
if (Number.isInteger(result)) return result
else {
store.commit(UserMutation.LOG_IN_USER, payload)
return true
}
})
},
[UserAction.LOGOUT]: (store) => {
return LogoutUserCommand.execute(store.state).then((result) => {
store.commit(UserMutation.LOG_OUT_USER)
return result
})
}
}
}
Actions
COMMAND - state-less object responsible for one operation only - extracted function
class LoginUserCommand {
execute (name, password) {
let db = Database.getApplicationInstance()
return db.logIn(name, password).then((response) => {
if (response.ok) { // {"ok":true,"name":"david","roles":[]}
return response
} else return UserError.LOG_IN_FAILED
})
.catch((error) => {
if (error.name === 'unauthorized' || error.name === 'forbidden') {
return UserError.LOG_IN_BAD_CREDITS // name or password incorrect
} else {
return UserError.LOG_IN_UNEXPECTED // cosmic rays, a meteor, etc.
}
})
}
}
const SINGLETON = new LoginUserCommand()
export default SINGLETON
Actions
COMPONENT
// dispatch with a payload
this.$store.dispatch('incrementAsync', {
amount: 10
})
// dispatch with an object
this.$store.dispatch({
type: 'incrementAsync',
amount: 10
})
<template>
<header>
<span>Gallery PWA</span>
<span v-if="$route.path!=='/'"><router-link to="/" exact>Home</router-link></span>
<span v-if="$route.path==='/' && !logged"><router-link to="/entrance">Enter</router-link></span>
<span v-if="logged"><a href="#" @click="onExit">Exit</a></span>
</header>
</template>
<script>
import ApplicationAction from '@/consts/actions/ApplicationAction'
import PageNames from '@/consts/PageNames'
import { mapState, mapActions } from 'vuex'
export default {
name: 'Header',
computed: {
...mapState(['logged'])
},
methods: {
...mapActions({ exitAction: ApplicationAction.EXIT }),
onExit () { this.exitAction().then(() => this.$router.push({ name: PageNames.EXIT })) }
}
}
</script>
Dispatch example
Mutations
Instead of mutating the state, actions commit mutations.
Mutations
Instead of mutating the state, actions commit mutations.
mutations: {
[GalleryMutation.DESTROY]: (state) => { for (let key in state) delete state[key] },
[GalleryMutation.SETUP_GALLERY]: (state, payload) => { Object.assign(state, payload) },
[GalleryMutation.SET_SELECTED_ITEM]: (state, payload) => { state.selectedItem = payload },
[GalleryMutation.UPDATE_GALLERY_VIEW]: (state, payload) => { state.view = payload },
[GalleryMutation.UPDATE_GALLERY_VIEW_INDEX]: (state, payload) => { state.index += payload }
}
import { mapMutations } from 'vuex'
export default {
// ...
methods: {
...mapMutations({
mutationUpdateGalleryViewIndex: GalleryMutation.UPDATE_GALLERY_VIEW_INDEX
}),
OnNavigateBack () { this.mutationUpdateGalleryViewIndex(-1) }
}
}
...
export const SET_SELECTED_ITEM = 'mutation_gallery_set_selected_item'
export const UPDATE_GALLERY_VIEW_INDEX = 'mutation_gallery_update_view_index'
export default {
...
SET_SELECTED_ITEM,
UPDATE_GALLERY_VIEW_INDEX
}
Modules
Each module can contain its own state, mutations, actions, getters, and even nested modules
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
})
store.state.a // -> `moduleA`'s state
store.state.b // -> `moduleB`'s state
Inside a module's mutations and getters, the first argument received will be the module's local state.
const moduleA = {
state: { count: 0 },
mutations: {
increment (state) {
state.count++
}
},
getters: {
doubleCount (state) {
return state.count * 2
}
},
actions: {
incrementIfOddOnRootSum ({ state, commit, rootState }) {
if ((state.count + rootState.count) % 2 === 1) {
commit('increment')
}
}
}
}
access root state
Modules
Namespacing
By default, actions, mutations and getters inside modules are registered under the global namespace - this allows multiple modules to react to the same mutation/action type.
Modules will be self-contained or reusable, when marked as namespaced with namespaced: true. When the module is registered, all of its getters, actions and mutations will be automatically namespaced based on the path the module is registered at.
Modules
Helpers with Namespace
computed: {
...mapState({
a: state => state.some.nested.module.a,
b: state => state.some.nested.module.b
})
},
methods: {
...mapActions([
'some/nested/module/foo',
'some/nested/module/bar'
])
}
computed: {
...mapState('some/nested/module', {
a: state => state.a,
b: state => state.b
})
},
methods: {
...mapActions('some/nested/module', [
'foo',
'bar'
])
}
import { createNamespacedHelpers } from 'vuex'
const { mapState, mapActions } = createNamespacedHelpers('some/nested/module')
Modules
Dynamic Module Registration
store.registerModule('myModule', moduleObject)
// Access it: store.state.myModule
// register a nested module
store.registerModule(['nested', 'myModule'], moduleObject)
// Access it: store.state.nested.myModule
store.unregisterModule('myModule')
Note you cannot remove static modules (declared at store creation) with this method.
import('@/model/stores/DynamicModuleStore').then(response => {
let module = response.default
store.registerModule(StoreNames.DynamicModuleName, module)
})
Load module dynamically (on demand)
Modules
Strict Mode
const store = new Vuex.Store({
// ...
strict: process.env.NODE_ENV !== 'production'
})
In strict mode, whenever Vuex state is mutated outside of mutation handlers, an error will be thrown.
Do not enable strict mode when deploying for production! Strict mode runs a synchronous deep watcher on the state tree for detecting inappropriate mutations, and it can be quite expensive when you make large amount of mutations to the state.
Modules
VUE DEV TOOL
Helps map Vue components to the routes and let vue-router know where to render them.
<div id="app">
<p>
<router-link to="/foo">Go to Foo</router-link>
<router-link to="/bar">Go to Bar</router-link>
</p>
<router-view></router-view>
</div>
// 1. Define route components.
// These can be imported from other files
const Foo = { template: '<div>foo</div>' }
const Bar = { template: '<div>bar</div>' }
// 2. Define some routes
const routes = [
{ path: '/foo', component: Foo },
{ path: '/bar', component: Bar }
]
// 3. Create the router instance and pass the `routes` option
const router = new VueRouter({ routes })
// 4. Create and mount the root instance.
const app = new Vue({
router
}).$mount('#app')
EXAMPLE
declare type RouteConfig = {
path: string;
component?: Component;
name?: string; // for named routes
components?: { [name: string]: Component }; // for named views
redirect?: string | Location | Function;
props?: boolean | string | Function;
alias?: string | Array<string>;
children?: Array<RouteConfig>; // for nested routes
beforeEnter?: (to: Route, from: Route, next: Function) => void;
meta?: any;
// 2.6.0+
caseSensitive?: boolean; // use case sensitive match? (default: false)
pathToRegexpOptions?: Object; // path-to-regexp options for compiling regex
}
ROUTE OBJECT
const isAuthorized = function (next, redirect, reverse = false) {
return Database.isAuthorized()
.then(user => {
let authorized = (user != null)
if (reverse ? !authorized : authorized) next()
else Router.replace({ name: redirect })
})
}
const Router = new VueRouter({
routes: [
{
path: '/',
name: PageNames.INDEX,
component: IndexPage
},
{
path: '/entrance',
name: PageNames.ENTRANCE,
component: () => import('@/view/pages/EntrancePage'),
beforeEnter (to, from, next) { isAuthorized(next, PageNames.INDEX, true) }
}
],
mode: 'history'
})
BEFORE ENTER - isAuthorized
HISTORY MODE
The default mode for vue-router is hash mode - it uses the URL hash to simulate a full URL so that the page won't be reloaded when the URL changes.
To get rid of the hash, we can use the router's history mode, which leverages the history.pushState API to achieve URL navigation without a page reload:
const router = new VueRouter({
mode: 'history',
routes: [...]
})
It enables applications to store data locally while offline, then synchronize it with CouchDB and compatible servers when the application is back online, keeping the user's data in sync no matter where they next login.
PouchDB provides a fully asynchronous API.
db.get('mittens', function (error, doc) {
if (error) {
// oh noes! we got an error
} else {
// okay, doc contains our document
}
});
db.get('mittens').then(function (doc) {
// okay, doc contains our document
}).catch(function (err) {
// oh noes! we got an error
});
Whenever you put() a document, it must have an _id field so that you can retrieve it later.
db.put({
"_id": "mittens",
"name": "Mittens",
"occupation": "kitten",
"age": 3,
"hobbies": [
"playing with balls of yarn",
"lookin' hella cute"
]
});
db.get('mittens').then(function (doc) {
console.log(doc);
});
{
"name": "Mittens",
"occupation": "kitten",
"age": 3,
"hobbies": [
"playing with balls of yarn",
"chasing laser pointers",
"lookin' hella cute"
],
"_id": "mittens",
"_rev": "1-bea5fa18e06522d12026f4aee6b15ee4"
}
PUT / GET
UPDATE (PUT) must have same _rev
doc.age = 4;
doc._rev = "1-bea5fa18e06522d12026f4aee6b15ee4";
db.put(doc);
No "_rev" will be a cause of conflict! - HTTP 409
When you remove() a document, it's not really deleted; it just gets a _deleted attribute added to it with different _rev
=
PouchDB provides two methods for bulk operations -
single transaction
Can handle many operations at once!
allDocs():
- returns documents in order
- allows you to reverse the order
- filter by _id
- attachments
- slice and dice using ">" and "<" on the _id
- limit and skip
- startkey and endkey
...
Work with attachments either in base64-encoded format, or as a Blob.
content_type:
- 'text/plain'
- 'image/png'
- 'image/jpeg'
Attachments
db.put({
_id: 'mydoc',
_attachments: {
'myattachment.txt': {
content_type: 'text/plain',
data: 'aGVsbG8gd29ybGQ='
}
}
});
btoa('hello world') // "aGVsbG8gd29ybGQ="
atob('aGVsbG8gd29ybGQ=') // "hello world"
db.get('mydoc', {attachments: true}).then(function (doc) {
console.log(doc);
});
{
"_attachments": {
"myattachment.txt": {
"content_type": "text/plain",
"digest": "md5-XrY7u+Ae7tCTyyK7j1rNww==",
"data": "aGVsbG8gd29ybGQ="
}
},
"_id": "mydoc",
"_rev": "1-e8a84187bb4e671f27ec11bdf7320aaa"
}
To get the full attachments when using get() or allDocs(), you need to specify {attachments: true}
Changes
db.changes({
since: 0,
include_docs: true
}).then(function (changes) { // Single-shot
// handle result
}).catch(function (err) {
// handle errors
});
var changes = db.changes({
since: 'now',
live: true,
include_docs: true
}).on('change', function(change) {
// handle change
if (change.deleted) {
// document was deleted
} else {
// document was added/modified
}
}).on('complete', function(info) {
// changes() was canceled
}).on('error', function (err) {
console.log(err);
});
changes.cancel(); // whenever you want to cancel
db.changes({
filter: function (doc) {
return doc.type === 'marsupial';
}
});
By default, the documents themselves are not included in the changes feed; only the ids, revs, and whether or not they were deleted.
With {include_docs: true}, each non-deleted change will have a doc property containing the new or modified document.
There are two types of changes:
- Added or modified documents
- Deleted documents
Conflicts _revs are what makes sync work so well
PouchDB and CouchDB's document revision structure is very similar to Git's.
In fact, each document's revision history is stored as a tree (exactly like Git), which allows you to handle conflicts when any two databases get out of sync.
Conflicts Immediate conflicts
db.put(myDoc).then(function () {
// success
}).catch(function (err) {
if (err.name === 'conflict') {
// conflict!
} else {
// some other error
}
});
db.upsert('my_id', myDeltaFunction).then(function () {
// success!
}).catch(function (err) {
// error (not a 404 or 409)
});
function myDeltaFunction(doc) {
doc.counter = doc.counter || 0;
doc.counter++;
return doc;
}
Resolved with upsert
("update or insert")
upsert() function takes a docId and deltaFunction, where the deltaFunction is just a function that takes a document and outputs a new document.
Conflicts Eventual conflicts
db.get('docid', {conflicts: true}).then(function (doc) {
// do something with the doc
}).catch(function (err) { /* handle any errors */ });
{
"_id": "docid",
"_rev": "2-x",
"_conflicts": ["2-y"]
}
db.get('docid', {rev: '2-y'}).then(function (doc) {
// RESOLVE IT HERE
}).catch(function (err) { /* handle any errors */ });
db.remove('docid', '2-y').then(function (doc) {
// yay, we're done
}).catch(function (err) { /* handle any errors */ });
Two PouchDB databases have both gone offline. Users make modifications to the same document. Users come back online.
1. To fetch the losing revision - get() it using the rev option
2. Resolve the conflict automatically using conflict resolution strategy: last write wins, first write wins, RCS, etc.
3. To mark a conflict as resolved - remove() the unwanted revisions.
4. If you want to resolve the conflict by creating a new revision - put() a new document on top of the current winner (make sure that the losing revision is deleted).
1
3
Replication CouchDB sync
multi-master architecture
any node can be written to or read from
var localDB = new PouchDB('mylocaldb')
var remoteDB = new PouchDB('http://localhost:5984/myremotedb')
var replicationHandler = localDB.replicate.to(remoteDB)
.on('complete', function () {
// yay, we're done!
}).on('error', function (err) {
// boo, something went wrong!
});
replicationHandler.cancel(); // <-- this cancels it
localDB.replicate.to(remoteDB);
localDB.replicate.from(remoteDB);
localDB.sync(remoteDB, {
live: true,
retry: true
}).on('change', function (change) {
// yo, something changed!
}).on('paused', function (info) {
// replication was paused, usually because of a lost connection
}).on('active', function (info) {
// replication was resumed
}).on('error', function (err) {
// totally unhandled error (shouldn't happen)
});
{live: true} enable live replication
{retry: true} retry until the connection is re-established
UNCOVERED
1. Mango queries (pouchdb-find):
- db.createIndex
- db.find (selector, sort, limit)
2. Map/reduce queries:
- db.query(function(){}, {key:"name"})
- db.query("some_index/by_name")
3. Compacting and destroying
- db.compact() , {auto_compaction: true}
- db.destroy()
4. Local documents
- db.put({_id: '_local/something', value: {}})
5. Adapters
6. Plugins
7. Default Settings
PouchDB Authentication
CouchDB is more than a database: it's also a RESTful web server with a built-in authentication framework.
Security features:
NODEJS
The express-pouchdb module is a fully qualified Express application with routing defined to mimic most of the CouchDB REST API, and whose behavior is handled by PouchDB.
npm install express-pouchdb pouchdb express
var PouchDB = require('pouchdb');
var express = require('express');
var app = express();
app.use('/db', require('express-pouchdb')(PouchDB));
app.listen(3000);
node app.js &
curl localhost:3000/db
Seamless multi-master sync, that scales from Big Data to Mobile, with an Intuitive HTTP/JSON API and designed for Reliability.
CouchDB
CouchDB
CouchDB
CouchDB
The CAP theorem identifies three distinct concerns:
- Consistency: all database clients see the same data, even with concurrent updates.
- Availability: all database clients are able to access some version of the data.
- Partition tolerance: the database can be split over multiple servers.
Pick two.
CouchDB - No Locking
CouchDB can run at full speed,
all the time, even under high load.
Requests are run in parallel.
CouchDB - No Locking
CouchDB uses Multi-Version Concurrency Control (MVCC) to manage concurrent access to the database.
Documents in CouchDB are versioned
CouchDB - Change
Consider a set of requests wanting to access a document:
CouchDB - Change
If you want to change a value in a document, you create an entire new version of that document and save it over the old one. After doing this, you end up with two versions of the same document, one old and one new.
CouchDB - Change
CouchDB - FAUXTON
Fauxton is a native web-based interface built into CouchDB. It provides a basic interface to the majority of the functionality, including the ability to create, update, delete and view documents and design documents. It provides access to the configuration parameters, and an interface for initiating replication.
http://127.0.0.1:5984/_utils
CouchDB - UNCOVERED
vue-webpack-boilerplate
$ npm install -g vue-cli
$ vue init webpack my-project
$ cd my-project
$ npm install
$ npm run dev
A full-featured Webpack setup with hot-reload, lint-on-save, unit testing &
What's Included
npm run dev: first-in-class development experience.
Lint-on-save with ESLint
What's Included
npm run build: Production ready build.
What's Included
1. consts - all hardcoded strings are located
2. controller - commands and business logic
3. model - data (vos, dtos), stores and services
4. view - visual parts (pages, components)
1
2
3
4
Every hardcoded string that is not changing in runtime must be extracted to separate reusable object
1. <transition> - smooth "fade" animation
2. <router-view> - page socket
3. <PreLoader> - initial loader
ApplicationStore has already been created when import happened, it's become functional when App exported, so we can access the functionality before everything will be displayed on the screen.
No functionality or data presentation before module will be loaded
and initialized inside the main store - store.registerModuleI(name, object)
import('@/model/stores/UserStore').then(module => {
let moduleDTO = new ModuleDTO(module.default)
store.dispatch(ApplicationAction.REGISTER_MODULE, moduleDTO)
...
[ApplicationAction.REGISTER_MODULE] (store, payload) {
console.log('> ApplicationStore -> ApplicationAction.REGISTER_MODULE payload =', payload)
if (payload && payload instanceof ModuleDTO) {
let module = payload.module
let moduleName = module.name
registeredModules.push(module)
this.registerModule(moduleName, module)
module.onRegister && module.onRegister(this._modules.root.getChild(moduleName).context)
}
}
There is
no gallery or user data
after module being loaded
galleryVO
userVO
before module loaded
Defines navigation and dynamically load appropriate components (on demand) in case it's allowed
using "history" mode
2
1
Note: may be defined in a main.js to be able to access ApplicationStore state
1
2
3
4
If an action requires complex data changes or requesting data outside of the app (or database) we encapsulate this functionality into Command.
Store responsible only for splitting and triggering data mutation.
Business logic happens in the command
action
1
2
3
State-less object responsible for one action:
1
2
3
4
5 resolve(new GalleryViewVO(pagesLimit, galleryItems))
6 - integer error key
7
[ApplicationAction.REGISTER_MODULE] (store, payload) {
...
this.registerModule(moduleName, module)
module.onRegister && module.onRegister(this._modules.root.getChild(moduleName).context)
}
}