SPA with Vue Stack

prepared by Vladimir Cores

Vue | Vuex | Router

COMPONENTS

STORES

NAVIGATIONS

  •  Components (single file)
  •  Separation of responsibility
  •  Tree / Branches Structure
  •  Advanced Routing
  •  VUE Debug Tools
  •  ESLint, Hot-Reload, WebPack
  •  NPM modules and UI libraries
  •  Full Documentation
  •  VUE and JS community
  •  IDEA / WebStorm
  •  JOY of development

Benefits

ue

Vue.js is focused on the ViewModel layer of the MVVM pattern.
It connects the View and the Model via two-way data bindings.

ue

Life Cycle

- beforeCreate

- created

- beforeMount

- mounted

       - beforeUpdate

       - updated

- beforeDestroy

- destroyed

ue

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

ue

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>

ue

Change Tracking

Vue performs DOM updates asynchronously.

ue

Change Tracking

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.

ue

Template {{ Mustaches }}

<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>

ue

Template: Directives

Directive attribute values are expected to be a single JavaScript expression

v-text | v-html | v-show | v-if | v-else | v-else-if | v-for | v-on | v-bind | v-model | v-pre | v-cloak | v-once |

A directive’s job is to reactively apply side effects to the DOM when the value of its expression changes

ue

Template: Directives (v-bind, v-on, v-model)

Arguments : v-bind ​<a :href="url"> ... </a>

Events @ v-on ​<a @click="doSomething"> ... </a>

Modifiers​

special postfixes denoted by a dot

*shorthands

ue

Template: class and style

<div :class="{

     active: isActive,

     'text-danger': hasError

}"/>

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'
    }
  }
}

<div :class="[

    activeClass,

    errorClass

]"/>

<div v-bind:class="classObject"/>

<div v-bind:class="styleObject"/>

1

1

2

2

3

4

4

ue

Template: Conditional Rendering

<h1 v-if="Math.random() > 0.5">Yes</h1>
<h2 
v-else-if="role === 'admin'">Admin</h2>

<h3 v-else>No</h3>

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.

ue

Template: Loops

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.

ue

Script: Components

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.

ue

Script: Async Components

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
})

ue

Script: Dynamic Props

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

ue

Script: Prop Validation

  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

ue

Script: Events

Every Vue instance implements an events interface:

  • Listen to an event using @on(eventName) = v-on:eventName
  • Trigger an event using $emit(eventName, optionalPayload)
<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 })
    }
  }
})

ue

Script: Methods

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

ue

Script: Computed

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.

ue

Script: Computed (Set and Get)

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]
    }
  }
}

ue

Script: Ref

<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.

ue

NOT COVERED:

- SLOTS

- MIXINS

- WATCHERS

- TRANSITIONS

- CUSTOM DIRECTIVES

- RENDER FUNCTIONS & JSX

- PLUGINS

- FILTERS

ue

CHEAT

SHEET

Link

GitHub

github.com/vuejs-tips/cheatsheet

uex

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.

uex

multiple components can share common state

uex

Multiple components that share common state:

  • Multiple views may depend on the same piece of state.
  • Actions from different views may need to mutate the same piece of 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

uex

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.

uex

- State
- Getters

- Actions
- Mutations
- Modules​

link : vuejs-tips.github.io/vuex-cheatsheet/

uex

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

uex

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'
})

uex

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 }
  }
})

uex

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>

uex

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

uex

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
      })
    }
  }
}

uex

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

uex

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

uex

Mutations

  1. The only way to actually change state in a Vuex store is by committing a mutation.
     
  2. When we mutate the state, Vue components observing the state will update automatically.
     
  3. Mutations Must Be Synchronous

Instead of mutating the state, actions commit mutations.

uex

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
}

uex

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

uex

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.

uex

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')

uex

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)

uex

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.

uex

Modules

VUE DEV TOOL

  1. State display
  2. Time traveling
  3. Last mutation
  4. Getters results

ue - router

Helps map Vue components to the routes and let vue-router know where to render them.

ue - router

<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

ue - router

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

ue - router

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

ue - router

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 - bulkDocs() for bulk writes, and allDocs() for bulk reads.

 

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

API:

    - db.signUp()

    - db.logIn()

    - db.logOut()

    - db.getSession()

    - db.getUser()

    - db.putUser()

    - db.deleteUser()

    - db.changePassword()

    - db.changeUsername()

    - db.signUpAdmin()

    - db.deleteAdmin()

  •  

CouchDB is more than a database: it's also a RESTful web server with a built-in authentication framework.

 

Security features:

  • salts and hashes passwords automatically
  • stores a cookie in the browser
  • refreshes the cookie every 10 minutes (default)

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

  1. Server
  2. Databases
  3. Documents
  4. Replication​

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:

  • The first request reads the document.
  • While this is being processed, a second request changes the document.
  • Since the second request includes a completely new version of the document, CouchDB can simply append it to the database without having to wait for the read request to finish.
  • When a third request wants to read the same document, CouchDB will point it to the new version that has just been written.

    During this whole process, the first request could still be reading the original version.

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

A read request will always see the most recent snapshot of your database.

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

  • Design Documents
  • Finding Your Data with Views
  • Validation Functions
  • Show Functions
  • Transforming Views with List Functions
  • Scaling
  • Replication
  • Conflict Management
  • Load Balancing
  • Clustering
  • Security
  • High Performance

PROJECT BOILERPLATE

boilerplate

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 & css extraction.

boilerplate

What's Included

npm run dev: first-in-class development experience.

  • Webpack + vue-loader for single file Vue components.
  • State preserving hot-reload
  • State preserving compilation error overlay
  • Lint-on-save with ESLint
  • Source maps

boilerplate

Lint-on-save with ESLint

boilerplate

What's Included

npm run build: Production ready build.

  • JavaScript minified with UglifyJS v3.
  • HTML minified with html-minifier.
  • CSS across all components extracted into a single file and minified with cssnano.
  • Static assets compiled with version hashes for efficient long-term caching, and an auto-generated production index.html with proper URLs to these generated assets.
  • Use npm run build --reportto build with bundle size analytics.

boilerplate

What's Included

npm run unit: Unit tests run in JSDOM with Jest, or in PhantomJS with Karma + Mocha + karma-webpack.
 

  • Supports ES2015+ in test files.
  • Easy mocking.

PROJECT OVERVIEW


future template

https://goo.gl/r6PaVQ

Structure

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

Consts

Every hardcoded string that is not changing in runtime must be extracted to separate reusable object

Main

  1. Import and initialize an App component
  2. Setup plugins and services
  3. Create Vue instance and render App component inside
  4. Set router
  5. beforeCreate - initialize the application with required data (user, server)

1

2

3

4

App

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.

ApplicationStore

  1. State is a main ValueObject shared across the all components,  (tree trunk)
     
  2. Responsible for common functionality (though actions) and dynamic module registering, for example UserStore
     
  3. State changes only in mutations

ApplicationStore

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

Router

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

GalleryPage

  1. Store was loaded/imported as a part of the component and being registered before rendering
     
  2. Act as a controller for multiple components, spreading the functionality (actions) and state of GalleryStore between them
     
  3. Dynamically loaded other components (UserSettings)
     
  4. Have it is own state to control self components

1

2

3

4

GalleryStore

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

Command

State-less object responsible for one action:

  1. can have it's own private functions (must be "bind")
  2. "execute" method is entry point with params
  3. usually return Promise((resolve, reject) => {})
  4. can use another services or libraries (like axios)
  5. resolve with data-object or integer error key
  6. reject with an integer (error key)
  7. exported as singleton
  8. can call another command(s)

1

2

3

4

5 resolve(new GalleryViewVO(pagesLimit, galleryItems))

6 - integer error key

7

DB Listeners

[ApplicationAction.REGISTER_MODULE] (store, payload) {
  ...
    this.registerModule(moduleName, module)
    module.onRegister && module.onRegister(this._modules.root.getChild(moduleName).context)
  }
}
Made with Slides.com