├── .gitignore
├── circle.yml
├── tests
├── .eslintrc
├── helpers.js
└── wildvue.spec.js
├── ci.sh
├── .eslintrc
├── karma.conf.js
├── LICENSE
├── examples
└── todoApp
│ └── index.html
├── package.json
├── dist
├── wildvue.min.js
└── wildvue.js
├── README.md
└── src
└── wildvue.js
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | node_modules
3 | coverage
4 |
--------------------------------------------------------------------------------
/circle.yml:
--------------------------------------------------------------------------------
1 | machine:
2 | node:
3 | version: 5
4 |
5 | test:
6 | override:
7 | - bash ./ci.sh
8 |
--------------------------------------------------------------------------------
/tests/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "env": {
3 | "mocha": true
4 | },
5 | "globals": {
6 | "expect": true,
7 | "sinon": true
8 | }
9 | }
10 |
--------------------------------------------------------------------------------
/ci.sh:
--------------------------------------------------------------------------------
1 | set -e
2 | if [ -z "$CI_PULL_REQUEST" ]
3 | then
4 | npm test
5 | cat ./coverage/lcov.info | ./node_modules/.bin/codecov
6 | else
7 | npm test
8 | fi
9 |
--------------------------------------------------------------------------------
/.eslintrc:
--------------------------------------------------------------------------------
1 | {
2 | "extends": "standard",
3 | "plugins": ["html"],
4 | "env": {
5 | "amd": true
6 | },
7 | "rules": {
8 | "no-new": 0
9 | }
10 | }
11 |
--------------------------------------------------------------------------------
/tests/helpers.js:
--------------------------------------------------------------------------------
1 | exports.invalidFirebaseRefs = [null, undefined, true, false, [], 0, 5, '', 'a', ['hi', 1]]
2 |
3 | /* Returns a random alphabetic string of variable length */
4 | exports.generateRandomString = function () {
5 | var possibleCharacters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
6 | var numPossibleCharacters = possibleCharacters.length
7 |
8 | var text = ''
9 | for (var i = 0; i < 10; i++) {
10 | text += possibleCharacters.charAt(Math.floor(Math.random() * numPossibleCharacters))
11 | }
12 |
13 | return text
14 | }
15 |
--------------------------------------------------------------------------------
/karma.conf.js:
--------------------------------------------------------------------------------
1 | var path = require('path')
2 |
3 | module.exports = function (config) {
4 | config.set({
5 | frameworks: ['mocha', 'sinon-chai'],
6 | browsers: ['PhantomJS'],
7 | reporters: ['spec', 'coverage'],
8 | files: [
9 | 'tests/wildvue.spec.js'
10 | ],
11 | preprocessors: {
12 | 'tests/wildvue.spec.js': ['webpack', 'sourcemap']
13 | },
14 | client: {
15 | mocha: {
16 | timeout: 100000
17 | }
18 | },
19 | browserNoActivityTimeout: 100000,
20 | webpack: {
21 | devtool: '#inline-source-map',
22 | module: {
23 | loaders: [{
24 | include: path.resolve(__dirname, 'src/wildvue.js'),
25 | loader: 'istanbul-instrumenter'
26 | }]
27 | }
28 | },
29 | webpackMiddleware: {
30 | noInfo: true
31 | },
32 | coverageReporter: {
33 | reporters: [
34 | { type: 'lcov', subdir: '.' },
35 | { type: 'text-summary' }
36 | ]
37 | }
38 | })
39 | }
40 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2016 Evan You
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/examples/todoApp/index.html:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | WildVue Todo App Demo
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 | -
15 | {{ item.text }}
16 |
17 |
18 |
19 |
23 |
24 |
25 |
52 |
53 |
54 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "wildvue",
3 | "version": "1.1.1",
4 | "description": "Wilddog bindings for Vue.js",
5 | "main": "dist/wildvue.js",
6 | "files": [
7 | "dist/**",
8 | "LICENSE",
9 | "README.md"
10 | ],
11 | "scripts": {
12 | "lint": "eslint --ext=js,html src tests examples karma.conf.js",
13 | "test": "npm run lint && karma start karma.conf.js --single-run",
14 | "dev": "webpack src/wildvue.js dist/wildvue.js --watch",
15 | "build-dev": "webpack src/wildvue.js dist/wildvue.js --output-library=WildVue --output-library-target=umd",
16 | "build-prod": "webpack src/wildvue.js dist/wildvue.min.js --output-library=WildVue --output-library-target=umd -p",
17 | "build": "npm run build-dev && npm run build-prod"
18 | },
19 | "repository": {
20 | "type": "git",
21 | "url": "git+https://github.com/vuejs/wildvue.git"
22 | },
23 | "keywords": [
24 | "vue",
25 | "mixin",
26 | "wilddog",
27 | "realtime"
28 | ],
29 | "author": "wilddog",
30 | "license": "MIT",
31 | "bugs": {
32 | "url": "https://github.com/vuejs/wildvue/issues"
33 | },
34 | "homepage": "https://github.com/vuejs/wildvue#readme",
35 | "devDependencies": {
36 | "chai": "^3.5.0",
37 | "codecov": "^1.0.1",
38 | "eslint": "^3.0.0",
39 | "eslint-config-standard": "^5.1.0",
40 | "eslint-plugin-html": "^1.5.2",
41 | "eslint-plugin-promise": "^3.3.0",
42 | "eslint-plugin-standard": "^2.0.1",
43 | "istanbul-instrumenter-loader": "^0.2.0",
44 | "karma": "^0.13.22",
45 | "karma-coverage": "^0.5.5",
46 | "karma-mocha": "^0.2.2",
47 | "karma-phantomjs-launcher": "^1.0.0",
48 | "karma-sinon-chai": "^1.2.0",
49 | "karma-sourcemap-loader": "^0.3.7",
50 | "karma-spec-reporter": "0.0.24",
51 | "karma-webpack": "^1.8.0",
52 | "lolex": "^1.4.0",
53 | "mocha": "^2.4.5",
54 | "phantomjs-prebuilt": "^2.1.3",
55 | "sinon": "^1.17.3",
56 | "sinon-chai": "^2.8.0",
57 | "vue": "^1.0.19",
58 | "webpack": "^1.12.14",
59 | "wilddog": "^2.3.10"
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/dist/wildvue.min.js:
--------------------------------------------------------------------------------
1 | !function(e,o){"object"==typeof exports&&"object"==typeof module?module.exports=o():"function"==typeof define&&define.amd?define([],o):"object"==typeof exports?exports.WildVue=o():e.WildVue=o()}(this,function(){return function(e){function o(n){if(i[n])return i[n].exports;var t=i[n]={exports:{},id:n,loaded:!1};return e[n].call(t.exports,t,t.exports,o),t.loaded=!0,t.exports}var i={};return o.m=e,o.c=i,o.p="",o(0)}([function(e,o){function i(e){return"function"==typeof e.key?e.key():e.key}function n(e){return"function"==typeof e.ref?e=e.ref():"object"==typeof e.ref&&(e=e.ref),e}function t(e){return"[object Object]"===Object.prototype.toString.call(e)}function r(e){var o=e.val(),n=t(o)?o:{".value":o};return n[".key"]=i(e),n}function d(e,o){for(var i=0;i` 全局引入: 如果`Vue` 存在会自动安装。
8 |
9 | ``` html
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 | ```
19 |
20 | 2. 在模块化环境中,比如CommonJS
21 |
22 | ``` bash
23 | npm install vue wilddog wildvue --save
24 | ```
25 |
26 | ``` js
27 | var Vue = require('vue')
28 | var WildVue = require('wildvue')
29 | var wilddog = require('wilddog')
30 | // 在模块化环境中需要使用 user 安装
31 | Vue.use(WildVue)
32 | ```
33 |
34 | ## 使用
35 |
36 | ``` js
37 | var wilddogApp = wilddog.initializeApp({ ... })
38 | var sync = wilddogApp.sync()
39 | var vm = new Vue({
40 | el: '#demo',
41 | wilddog: {
42 | //简单语法
43 | //默认作为数组绑定
44 | anArray: sync.ref('url/to/my/collection'),
45 | // 也可以绑定一个查询
46 | // anArray: sync.ref('url/to/my/collection').limitToLast(25)
47 | // 完整语法
48 | anObject: {
49 | source: new Wilddog('url/to/my/object'),
50 | // 可选,作为对象绑定
51 | asObject: true,
52 | // 可选,提供一个回调
53 | cancelCallback: function () {}
54 | }
55 | }
56 | })
57 | ```
58 |
59 | ``` html
60 |
61 |
{{ anObject | json }}
62 |
63 | - {{ item.text }}
64 |
65 |
66 | ```
67 |
68 | 上面的例子会绑定一个Vue实例的`anObject` 和 `anArray` 到相应的 Wilddog 数据源。另外,Vue实例也可以使用 `$wilddogRefs` property:
69 |
70 | ``` js
71 | // add an item to the array
72 | vm.$wilddogRefs.anArray.push({
73 | text: 'hello'
74 | })
75 | ```
76 |
77 | 另外,你也可以使用 `$bindAsObject` 或 `$bindAsArray` 方法绑定一个Wildog ref:
78 |
79 | ``` js
80 | vm.$bindAsObject('user', myWilddogRef.child('user'))
81 | vm.$bindAsArray('items', myWilddogRef.child('items').limitToLast(25))
82 | ```
83 |
84 | ## 数据归一化
85 |
86 | ### 数组绑定
87 |
88 | 在绑定数组中的每一条记录中都会包含一个 `.key` property,用来表示这条记录的key。 所以,如果你有书记在 `/items/-Jtjl482BaXBCI7brMT8/` , 这条记录会存在一个 `.key:"-Jtjl482BaXBCI7brMT8"`。
89 | 如果value是简单数据类型(boolean,string,number)会保存在`.value` property 中。如果value 是对象,value对象中每个property 都会直接存放在记录中:
90 |
91 | ``` json
92 | {
93 | "items": {
94 | "-Jtjl482BaXBCI7brMT8": 100,
95 | "-Jtjl6tmqjNeAnQvyD4l": {
96 | "first": "fred",
97 | "last": "Flintstone"
98 | },
99 | "-JtjlAXoQ3VAoNiJcka9": "foo"
100 | }
101 | }
102 | ```
103 |
104 | The resulting bound array stored in `vm.items` will be:
105 |
106 | ``` json
107 | [
108 | {
109 | ".key": "-Jtjl482BaXBCI7brMT8",
110 | ".value": 100
111 | },
112 | {
113 | ".key": "-Jtjl6tmqjNeAnQvyD4l",
114 | "first": "Fred",
115 | "last": "Flintstone"
116 | },
117 | {
118 | ".key": "-JtjlAXoQ3VAoNiJcka9",
119 | ".value": "foo"
120 | }
121 | ]
122 | ```
123 |
124 | ## 贡献
125 |
126 | Clone the repo, then:
127 |
128 | ```bash
129 | $ npm install # install dependencies
130 | $ npm test # run test suite with coverage report
131 | $ npm run dev # watch and build dist/wildvue.js
132 | $ npm run build # build dist/wildvue.js and wildvue.min.js
133 | ```
134 | ## License
135 |
136 | [MIT](http://opensource.org/licenses/MIT)
--------------------------------------------------------------------------------
/src/wildvue.js:
--------------------------------------------------------------------------------
1 | var Vue // late binding
2 |
3 | /**
4 | * Returns the key of a Wilddog snapshot across SDK versions.
5 | *
6 | * @param {WilddogSnapshot} snapshot
7 | * @return {string|null}
8 | */
9 | function _getKey (snapshot) {
10 | return typeof snapshot.key === 'function'
11 | ? snapshot.key()
12 | : snapshot.key
13 | }
14 |
15 | /**
16 | * Returns the original reference of a Wilddog reference or query across SDK versions.
17 | *
18 | * @param {WilddogReference|WilddogQuery} refOrQuery
19 | * @return {WilddogReference}
20 | */
21 | function _getRef (refOrQuery) {
22 | if (typeof refOrQuery.ref === 'function') {
23 | refOrQuery = refOrQuery.ref()
24 | } else if (typeof refOrQuery.ref === 'object') {
25 | refOrQuery = refOrQuery.ref
26 | }
27 |
28 | return refOrQuery
29 | }
30 |
31 | /**
32 | * Check if a value is an object.
33 | *
34 | * @param {*} val
35 | * @return {boolean}
36 | */
37 | function isObject (val) {
38 | return Object.prototype.toString.call(val) === '[object Object]'
39 | }
40 |
41 | /**
42 | * Convert wilddog snapshot into a bindable data record.
43 | *
44 | * @param {WilddogSnapshot} snapshot
45 | * @return {Object}
46 | */
47 | function createRecord (snapshot) {
48 | var value = snapshot.val()
49 | var res = isObject(value)
50 | ? value
51 | : { '.value': value }
52 | res['.key'] = _getKey(snapshot)
53 | return res
54 | }
55 |
56 | /**
57 | * Find the index for an object with given key.
58 | *
59 | * @param {array} array
60 | * @param {string} key
61 | * @return {number}
62 | */
63 | function indexForKey (array, key) {
64 | for (var i = 0; i < array.length; i++) {
65 | if (array[i]['.key'] === key) {
66 | return i
67 | }
68 | }
69 | /* istanbul ignore next */
70 | return -1
71 | }
72 |
73 | /**
74 | * Bind a wilddog data source to a key on a vm.
75 | *
76 | * @param {Vue} vm
77 | * @param {string} key
78 | * @param {object} source
79 | */
80 | function bind (vm, key, source) {
81 | var asObject = false
82 | var cancelCallback = null
83 | // check { source, asArray, cancelCallback } syntax
84 | if (isObject(source) && source.hasOwnProperty('source')) {
85 | asObject = source.asObject
86 | cancelCallback = source.cancelCallback
87 | source = source.source
88 | }
89 | if (!isObject(source)) {
90 | throw new Error('WildVue: invalid Wilddog binding source.')
91 | }
92 | // get the original ref for possible queries
93 | var ref = _getRef(source)
94 | vm.$wilddogRefs[key] = ref
95 | vm._wilddogSources[key] = source
96 | // bind based on initial value type
97 | if (asObject) {
98 | bindAsObject(vm, key, source, cancelCallback)
99 | } else {
100 | bindAsArray(vm, key, source, cancelCallback)
101 | }
102 | }
103 |
104 | /**
105 | * Define a reactive property in a given vm if it's not defined
106 | * yet
107 | *
108 | * @param {Vue} vm
109 | * @param {string} key
110 | * @param {*} val
111 | */
112 | function defineReactive (vm, key, val) {
113 | if (key in vm) {
114 | vm[key] = val
115 | } else {
116 | Vue.util.defineReactive(vm, key, val)
117 | }
118 | }
119 |
120 | /**
121 | * Bind a wilddog data source to a key on a vm as an Array.
122 | *
123 | * @param {Vue} vm
124 | * @param {string} key
125 | * @param {object} source
126 | * @param {function|null} cancelCallback
127 | */
128 | function bindAsArray (vm, key, source, cancelCallback) {
129 | var array = []
130 | defineReactive(vm, key, array)
131 |
132 | var onAdd = source.on('child_added', function (snapshot, prevKey) {
133 | var index = prevKey ? indexForKey(array, prevKey) + 1 : 0
134 | array.splice(index, 0, createRecord(snapshot))
135 | }, cancelCallback)
136 |
137 | var onRemove = source.on('child_removed', function (snapshot) {
138 | var index = indexForKey(array, _getKey(snapshot))
139 | array.splice(index, 1)
140 | }, cancelCallback)
141 |
142 | var onChange = source.on('child_changed', function (snapshot) {
143 | var index = indexForKey(array, _getKey(snapshot))
144 | array.splice(index, 1, createRecord(snapshot))
145 | }, cancelCallback)
146 |
147 | var onMove = source.on('child_moved', function (snapshot, prevKey) {
148 | var index = indexForKey(array, _getKey(snapshot))
149 | var record = array.splice(index, 1)[0]
150 | var newIndex = prevKey ? indexForKey(array, prevKey) + 1 : 0
151 | array.splice(newIndex, 0, record)
152 | }, cancelCallback)
153 |
154 | vm._wilddogListeners[key] = {
155 | child_added: onAdd,
156 | child_removed: onRemove,
157 | child_changed: onChange,
158 | child_moved: onMove
159 | }
160 | }
161 |
162 | /**
163 | * Bind a wilddog data source to a key on a vm as an Object.
164 | *
165 | * @param {Vue} vm
166 | * @param {string} key
167 | * @param {Object} source
168 | * @param {function|null} cancelCallback
169 | */
170 | function bindAsObject (vm, key, source, cancelCallback) {
171 | defineReactive(vm, key, {})
172 | var cb = source.on('value', function (snapshot) {
173 | vm[key] = createRecord(snapshot)
174 | }, cancelCallback)
175 | vm._wilddogListeners[key] = { value: cb }
176 | }
177 |
178 | /**
179 | * Unbind a wilddog-bound key from a vm.
180 | *
181 | * @param {Vue} vm
182 | * @param {string} key
183 | */
184 | function unbind (vm, key) {
185 | var source = vm._wilddogSources && vm._wilddogSources[key]
186 | if (!source) {
187 | throw new Error(
188 | 'WildVue: unbind failed: "' + key + '" is not bound to ' +
189 | 'a Wilddog reference.'
190 | )
191 | }
192 | var listeners = vm._wilddogListeners[key]
193 | for (var event in listeners) {
194 | source.off(event, listeners[event])
195 | }
196 | vm[key] = null
197 | vm.$wilddogRefs[key] = null
198 | vm._wilddogSources[key] = null
199 | vm._wilddogListeners[key] = null
200 | }
201 |
202 | /**
203 | * Ensure the related bookkeeping variables on an instance.
204 | *
205 | * @param {Vue} vm
206 | */
207 | function ensureRefs (vm) {
208 | if (!vm.$wilddogRefs) {
209 | vm.$wilddogRefs = Object.create(null)
210 | vm._wilddogSources = Object.create(null)
211 | vm._wilddogListeners = Object.create(null)
212 | }
213 | }
214 |
215 | var init = function () {
216 | var bindings = this.$options.wilddog
217 | if (!bindings) return
218 | ensureRefs(this)
219 | for (var key in bindings) {
220 | bind(this, key, bindings[key])
221 | }
222 | }
223 |
224 | var WildVueMixin = {
225 | init: init, // 1.x
226 | beforeCreate: init, // 2.x
227 | beforeDestroy: function () {
228 | if (!this.$wilddogRefs) return
229 | for (var key in this.$wilddogRefs) {
230 | if (this.$wilddogRefs[key]) {
231 | this.$unbind(key)
232 | }
233 | }
234 | this.$wilddogRefs = null
235 | this._wilddogSources = null
236 | this._wilddogListeners = null
237 | }
238 | }
239 |
240 | /**
241 | * Install function passed to Vue.use() in manual installation.
242 | *
243 | * @param {function} _Vue
244 | */
245 | function install (_Vue) {
246 | Vue = _Vue
247 | Vue.mixin(WildVueMixin)
248 |
249 | // use object-based merge strategy
250 | var mergeStrats = Vue.config.optionMergeStrategies
251 | mergeStrats.wilddog = mergeStrats.methods
252 |
253 | // extend instance methods
254 | Vue.prototype.$bindAsObject = function (key, source, cancelCallback) {
255 | ensureRefs(this)
256 | bind(this, key, {
257 | source: source,
258 | asObject: true,
259 | cancelCallback: cancelCallback
260 | })
261 | }
262 |
263 | Vue.prototype.$bindAsArray = function (key, source, cancelCallback) {
264 | ensureRefs(this)
265 | bind(this, key, {
266 | source: source,
267 | cancelCallback: cancelCallback
268 | })
269 | }
270 |
271 | Vue.prototype.$unbind = function (key) {
272 | unbind(this, key)
273 | }
274 | }
275 |
276 | // auto install
277 | /* istanbul ignore if */
278 | if (typeof window !== 'undefined' && window.Vue) {
279 | install(window.Vue)
280 | }
281 |
282 | module.exports = install
283 |
--------------------------------------------------------------------------------
/dist/wildvue.js:
--------------------------------------------------------------------------------
1 | (function webpackUniversalModuleDefinition(root, factory) {
2 | if(typeof exports === 'object' && typeof module === 'object')
3 | module.exports = factory();
4 | else if(typeof define === 'function' && define.amd)
5 | define([], factory);
6 | else if(typeof exports === 'object')
7 | exports["WildVue"] = factory();
8 | else
9 | root["WildVue"] = factory();
10 | })(this, function() {
11 | return /******/ (function(modules) { // webpackBootstrap
12 | /******/ // The module cache
13 | /******/ var installedModules = {};
14 |
15 | /******/ // The require function
16 | /******/ function __webpack_require__(moduleId) {
17 |
18 | /******/ // Check if module is in cache
19 | /******/ if(installedModules[moduleId])
20 | /******/ return installedModules[moduleId].exports;
21 |
22 | /******/ // Create a new module (and put it into the cache)
23 | /******/ var module = installedModules[moduleId] = {
24 | /******/ exports: {},
25 | /******/ id: moduleId,
26 | /******/ loaded: false
27 | /******/ };
28 |
29 | /******/ // Execute the module function
30 | /******/ modules[moduleId].call(module.exports, module, module.exports, __webpack_require__);
31 |
32 | /******/ // Flag the module as loaded
33 | /******/ module.loaded = true;
34 |
35 | /******/ // Return the exports of the module
36 | /******/ return module.exports;
37 | /******/ }
38 |
39 |
40 | /******/ // expose the modules object (__webpack_modules__)
41 | /******/ __webpack_require__.m = modules;
42 |
43 | /******/ // expose the module cache
44 | /******/ __webpack_require__.c = installedModules;
45 |
46 | /******/ // __webpack_public_path__
47 | /******/ __webpack_require__.p = "";
48 |
49 | /******/ // Load entry module and return exports
50 | /******/ return __webpack_require__(0);
51 | /******/ })
52 | /************************************************************************/
53 | /******/ ([
54 | /* 0 */
55 | /***/ function(module, exports) {
56 |
57 | var Vue // late binding
58 |
59 | /**
60 | * Returns the key of a Wilddog snapshot across SDK versions.
61 | *
62 | * @param {WilddogSnapshot} snapshot
63 | * @return {string|null}
64 | */
65 | function _getKey (snapshot) {
66 | return typeof snapshot.key === 'function'
67 | ? snapshot.key()
68 | : snapshot.key
69 | }
70 |
71 | /**
72 | * Returns the original reference of a Wilddog reference or query across SDK versions.
73 | *
74 | * @param {WilddogReference|WilddogQuery} refOrQuery
75 | * @return {WilddogReference}
76 | */
77 | function _getRef (refOrQuery) {
78 | if (typeof refOrQuery.ref === 'function') {
79 | refOrQuery = refOrQuery.ref()
80 | } else if (typeof refOrQuery.ref === 'object') {
81 | refOrQuery = refOrQuery.ref
82 | }
83 |
84 | return refOrQuery
85 | }
86 |
87 | /**
88 | * Check if a value is an object.
89 | *
90 | * @param {*} val
91 | * @return {boolean}
92 | */
93 | function isObject (val) {
94 | return Object.prototype.toString.call(val) === '[object Object]'
95 | }
96 |
97 | /**
98 | * Convert wilddog snapshot into a bindable data record.
99 | *
100 | * @param {WilddogSnapshot} snapshot
101 | * @return {Object}
102 | */
103 | function createRecord (snapshot) {
104 | var value = snapshot.val()
105 | var res = isObject(value)
106 | ? value
107 | : { '.value': value }
108 | res['.key'] = _getKey(snapshot);
109 | return res
110 | }
111 |
112 | /**
113 | * Find the index for an object with given key.
114 | *
115 | * @param {array} array
116 | * @param {string} key
117 | * @return {number}
118 | */
119 | function indexForKey (array, key) {
120 | for (var i = 0; i < array.length; i++) {
121 | if (array[i]['.key'] === key) {
122 | return i
123 | }
124 | }
125 | /* istanbul ignore next */
126 | return -1
127 | }
128 |
129 | /**
130 | * Bind a wilddog data source to a key on a vm.
131 | *
132 | * @param {Vue} vm
133 | * @param {string} key
134 | * @param {object} source
135 | */
136 | function bind (vm, key, source) {
137 | var asObject = false
138 | var cancelCallback = null
139 | // check { source, asArray, cancelCallback } syntax
140 | if (isObject(source) && source.hasOwnProperty('source')) {
141 | asObject = source.asObject
142 | cancelCallback = source.cancelCallback
143 | source = source.source
144 | }
145 | if (!isObject(source)) {
146 | throw new Error('WildVue: invalid Wilddog binding source.')
147 | }
148 | // get the original ref for possible queries
149 | var ref = _getRef(source)
150 | vm.$wilddogRefs[key] = ref
151 | vm._wilddogSources[key] = source
152 | // bind based on initial value type
153 | if (asObject) {
154 | bindAsObject(vm, key, source, cancelCallback)
155 | } else {
156 | bindAsArray(vm, key, source, cancelCallback)
157 | }
158 | }
159 |
160 | /**
161 | * Define a reactive property in a given vm if it's not defined
162 | * yet
163 | *
164 | * @param {Vue} vm
165 | * @param {string} key
166 | * @param {*} val
167 | */
168 | function defineReactive (vm, key, val) {
169 | if (key in vm) {
170 | vm[key] = val
171 | } else {
172 | Vue.util.defineReactive(vm, key, val)
173 | }
174 | }
175 |
176 | /**
177 | * Bind a wilddog data source to a key on a vm as an Array.
178 | *
179 | * @param {Vue} vm
180 | * @param {string} key
181 | * @param {object} source
182 | * @param {function|null} cancelCallback
183 | */
184 | function bindAsArray (vm, key, source, cancelCallback) {
185 | var array = []
186 | defineReactive(vm, key, array)
187 |
188 | var onAdd = source.on('child_added', function (snapshot, prevKey) {
189 | var index = prevKey ? indexForKey(array, prevKey) + 1 : 0
190 | array.splice(index, 0, createRecord(snapshot))
191 | }, cancelCallback)
192 |
193 | var onRemove = source.on('child_removed', function (snapshot) {
194 | var index = indexForKey(array, _getKey(snapshot))
195 | array.splice(index, 1)
196 | }, cancelCallback)
197 |
198 | var onChange = source.on('child_changed', function (snapshot) {
199 | var index = indexForKey(array, _getKey(snapshot))
200 | array.splice(index, 1, createRecord(snapshot))
201 | }, cancelCallback)
202 |
203 | var onMove = source.on('child_moved', function (snapshot, prevKey) {
204 | var index = indexForKey(array, _getKey(snapshot))
205 | var record = array.splice(index, 1)[0]
206 | var newIndex = prevKey ? indexForKey(array, prevKey) + 1 : 0
207 | array.splice(newIndex, 0, record)
208 | }, cancelCallback)
209 |
210 | vm._wilddogListeners[key] = {
211 | child_added: onAdd,
212 | child_removed: onRemove,
213 | child_changed: onChange,
214 | child_moved: onMove
215 | }
216 | }
217 |
218 | /**
219 | * Bind a wilddog data source to a key on a vm as an Object.
220 | *
221 | * @param {Vue} vm
222 | * @param {string} key
223 | * @param {Object} source
224 | * @param {function|null} cancelCallback
225 | */
226 | function bindAsObject (vm, key, source, cancelCallback) {
227 | defineReactive(vm, key, {})
228 | var cb = source.on('value', function (snapshot) {
229 | vm[key] = createRecord(snapshot)
230 | }, cancelCallback)
231 | vm._wilddogListeners[key] = { value: cb }
232 | }
233 |
234 | /**
235 | * Unbind a wilddog-bound key from a vm.
236 | *
237 | * @param {Vue} vm
238 | * @param {string} key
239 | */
240 | function unbind (vm, key) {
241 | var source = vm._wilddogSources && vm._wilddogSources[key]
242 | if (!source) {
243 | throw new Error(
244 | 'WildVue: unbind failed: "' + key + '" is not bound to ' +
245 | 'a Wilddog reference.'
246 | )
247 | }
248 | var listeners = vm._wilddogListeners[key]
249 | for (var event in listeners) {
250 | source.off(event, listeners[event])
251 | }
252 | vm[key] = null
253 | vm.$wilddogRefs[key] = null
254 | vm._wilddogSources[key] = null
255 | vm._wilddogListeners[key] = null
256 | }
257 |
258 | /**
259 | * Ensure the related bookkeeping variables on an instance.
260 | *
261 | * @param {Vue} vm
262 | */
263 | function ensureRefs (vm) {
264 | if (!vm.$wilddogRefs) {
265 | vm.$wilddogRefs = Object.create(null)
266 | vm._wilddogSources = Object.create(null)
267 | vm._wilddogListeners = Object.create(null)
268 | }
269 | }
270 |
271 | var init = function () {
272 | var bindings = this.$options.wilddog
273 | if (!bindings) return
274 | ensureRefs(this)
275 | for (var key in bindings) {
276 | bind(this, key, bindings[key])
277 | }
278 | }
279 |
280 | var WildVueMixin = {
281 | init: init, // 1.x
282 | beforeCreate: init, // 2.x
283 | beforeDestroy: function () {
284 | if (!this.$wilddogRefs) return
285 | for (var key in this.$wilddogRefs) {
286 | if (this.$wilddogRefs[key]) {
287 | this.$unbind(key)
288 | }
289 | }
290 | this.$wilddogRefs = null
291 | this._wilddogSources = null
292 | this._wilddogListeners = null
293 | }
294 | }
295 |
296 | /**
297 | * Install function passed to Vue.use() in manual installation.
298 | *
299 | * @param {function} _Vue
300 | */
301 | function install (_Vue) {
302 | Vue = _Vue
303 | Vue.mixin(WildVueMixin)
304 |
305 | // use object-based merge strategy
306 | var mergeStrats = Vue.config.optionMergeStrategies
307 | mergeStrats.wilddog = mergeStrats.methods
308 |
309 | // extend instance methods
310 | Vue.prototype.$bindAsObject = function (key, source, cancelCallback) {
311 | ensureRefs(this)
312 | bind(this, key, {
313 | source: source,
314 | asObject: true,
315 | cancelCallback: cancelCallback
316 | })
317 | }
318 |
319 | Vue.prototype.$bindAsArray = function (key, source, cancelCallback) {
320 | ensureRefs(this)
321 | bind(this, key, {
322 | source: source,
323 | cancelCallback: cancelCallback
324 | })
325 | }
326 |
327 | Vue.prototype.$unbind = function (key) {
328 | unbind(this, key)
329 | }
330 | }
331 |
332 | // auto install
333 | /* istanbul ignore if */
334 | if (typeof window !== 'undefined' && window.Vue) {
335 | install(window.Vue)
336 | }
337 |
338 | module.exports = install
339 |
340 |
341 | /***/ }
342 | /******/ ])
343 | });
344 | ;
--------------------------------------------------------------------------------
/tests/wildvue.spec.js:
--------------------------------------------------------------------------------
1 | var Vue = require('vue')
2 | var wilddog = require('wilddog')
3 | var WildVue = require('../src/wildvue')
4 | var helpers = require('./helpers')
5 |
6 | Vue.use(WildVue)
7 |
8 | var wilddogApp = wilddog.initializeApp({
9 | authDomain: 'test123.wilddog.com',
10 | syncURL: 'https://test123.wilddogio.com'
11 | })
12 | var sync = wilddogApp.sync()
13 |
14 | describe('WildVue', function () {
15 | var wilddogRef
16 | // network can be very slow
17 | this.timeout(0)
18 | beforeEach(function (done) {
19 | wilddogRef = sync
20 | wilddogRef.remove(function (error) {
21 | if (error && error.code !== 'OP_REPEAT') {
22 | done(error)
23 | } else {
24 | wilddogRef = wilddogRef.child(helpers.generateRandomString())
25 | done()
26 | }
27 | })
28 | })
29 |
30 | describe('bind as Array', function () {
31 | /* it('throws error for invalid wilddog ref', function () {
32 | helpers.invalidWilddogRefs.forEach(function (ref) {
33 | expect(function () {
34 | new Vue({
35 | wilddog: {
36 | items: ref
37 | }
38 | })
39 | }).to.throw('WildVue: invalid Wilddog binding source.')
40 | })
41 | })
42 | */
43 | it('binds array records which are objects', function (done) {
44 | var vm = new Vue({
45 | wilddog: {
46 | items: wilddogRef
47 | },
48 | template: '{{ item[".key"] }} {{ item.index }}
'
49 | }).$mount()
50 | wilddogRef.set({
51 | first: { index: 0 },
52 | second: { index: 1 },
53 | third: { index: 2 }
54 | }, function () {
55 | expect(vm.items).to.deep.equal([
56 | { '.key': 'first', index: 0 },
57 | { '.key': 'second', index: 1 },
58 | { '.key': 'third', index: 2 }
59 | ])
60 | Vue.nextTick(function () {
61 | expect(vm.$el.textContent).to.contain('first 0 second 1 third 2')
62 | done()
63 | })
64 | })
65 | })
66 |
67 | it('bind using $bindAsArray', function (done) {
68 | var vm = new Vue({
69 | template: '{{ item[".key"] }} {{ item.index }}
',
70 | created: function () {
71 | this.$bindAsArray('items', wilddogRef)
72 | }
73 | }).$mount()
74 | wilddogRef.set({
75 | first: { index: 0 },
76 | second: { index: 1 },
77 | third: { index: 2 }
78 | }, function () {
79 | expect(vm.items).to.deep.equal([
80 | { '.key': 'first', index: 0 },
81 | { '.key': 'second', index: 1 },
82 | { '.key': 'third', index: 2 }
83 | ])
84 | Vue.nextTick(function () {
85 | expect(vm.$el.textContent).to.contain('first 0 second 1 third 2')
86 | done()
87 | })
88 | })
89 | })
90 |
91 | it('binds array records which are primitives', function (done) {
92 | var vm = new Vue({
93 | wilddog: {
94 | items: wilddogRef
95 | },
96 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
97 | }).$mount()
98 | wilddogRef.set(['first', 'second', 'third'], function () {
99 | expect(vm.items).to.deep.equal([
100 | { '.key': '0', '.value': 'first' },
101 | { '.key': '1', '.value': 'second' },
102 | { '.key': '2', '.value': 'third' }
103 | ])
104 | Vue.nextTick(function () {
105 | expect(vm.$el.textContent).to.contain('0 first 1 second 2 third')
106 | done()
107 | })
108 | })
109 | })
110 |
111 | it('binds array records which are a mix of objects and primitives', function (done) {
112 | var vm = new Vue({
113 | wilddog: {
114 | items: wilddogRef
115 | },
116 | template: '{{ item[".key"] }} {{ item[".value"] }} {{ item.index}}
'
117 | }).$mount()
118 | wilddogRef.set({
119 | 0: 'first',
120 | 1: 'second',
121 | third: { index: 2 }
122 | }, function () {
123 | expect(vm.items).to.deep.equal([
124 | { '.key': '0', '.value': 'first' },
125 | { '.key': '1', '.value': 'second' },
126 | { '.key': 'third', index: 2 }
127 | ])
128 | Vue.nextTick(function () {
129 | expect(vm.$el.textContent).to.contain('0 first 1 second third 2')
130 | done()
131 | })
132 | })
133 | })
134 |
135 | it('binds array records which are a mix of objects and primitives', function (done) {
136 | var vm = new Vue({
137 | wilddog: {
138 | items: wilddogRef
139 | }
140 | })
141 | wilddogRef.set(null, function () {
142 | expect(vm.items).to.deep.equal([])
143 | done()
144 | })
145 | })
146 |
147 | it('binds sparse arrays', function (done) {
148 | var vm = new Vue({
149 | wilddog: {
150 | items: wilddogRef
151 | }
152 | })
153 | wilddogRef.set({ 0: 'a', 2: 'b', 5: 'c' }, function () {
154 | expect(vm.items).to.deep.equal([
155 | { '.key': '0', '.value': 'a' },
156 | { '.key': '2', '.value': 'b' },
157 | { '.key': '5', '.value': 'c' }
158 | ])
159 | done()
160 | })
161 | })
162 |
163 | it('binds only a subset of records when using limit queries', function (done) {
164 | var vm = new Vue({
165 | wilddog: {
166 | items: wilddogRef.limitToLast(2)
167 | },
168 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
169 | }).$mount()
170 | wilddogRef.set({ a: 1, b: 2, c: 3 }, function () {
171 | expect(vm.items).to.deep.equal([
172 | { '.key': 'b', '.value': 2 },
173 | { '.key': 'c', '.value': 3 }
174 | ])
175 | Vue.nextTick(function () {
176 | expect(vm.$el.textContent).to.contain('b 2 c 3')
177 | done()
178 | })
179 | })
180 | })
181 |
182 | it('removes records when they fall outside of a limit query', function (done) {
183 | var vm = new Vue({
184 | wilddog: {
185 | items: wilddogRef.limitToLast(2)
186 | },
187 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
188 | }).$mount()
189 | wilddogRef.set({ a: 1, b: 2, c: 3 }, function () {
190 | wilddogRef.child('d').set(4, function () {
191 | expect(vm.items).to.deep.equal([
192 | { '.key': 'c', '.value': 3 },
193 | { '.key': 'd', '.value': 4 }
194 | ])
195 | Vue.nextTick(function () {
196 | expect(vm.$el.textContent).to.contain('c 3 d 4')
197 | done()
198 | })
199 | })
200 | })
201 | })
202 |
203 | it('adds a new record when an existing record in the limit query is removed', function (done) {
204 | var vm = new Vue({
205 | wilddog: {
206 | items: wilddogRef.limitToLast(2)
207 | },
208 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
209 | }).$mount()
210 | wilddogRef.set({ a: 1, b: 2, c: 3 }, function () {
211 | wilddogRef.child('b').remove(function () {
212 | expect(vm.items).to.deep.equal([
213 | { '.key': 'a', '.value': 1 },
214 | { '.key': 'c', '.value': 3 }
215 | ])
216 | Vue.nextTick(function () {
217 | expect(vm.$el.textContent).to.contain('a 1 c 3')
218 | done()
219 | })
220 | })
221 | })
222 | })
223 |
224 | it('binds records in the correct order when using ordered queries', function (done) {
225 | var vm = new Vue({
226 | wilddog: {
227 | items: wilddogRef.orderByValue()
228 | },
229 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
230 | }).$mount()
231 | wilddogRef.set({ a: 2, b: 1, c: 3 }, function () {
232 | expect(vm.items).to.deep.equal([
233 | { '.key': 'b', '.value': 1 },
234 | { '.key': 'a', '.value': 2 },
235 | { '.key': 'c', '.value': 3 }
236 | ])
237 | Vue.nextTick(function () {
238 | expect(vm.$el.textContent).to.contain('b 1 a 2 c 3')
239 | done()
240 | })
241 | })
242 | })
243 |
244 | it('binds multiple Wilddog references to state variables at the same time', function (done) {
245 | var vm = new Vue({
246 | wilddog: {
247 | bindVar0: wilddogRef.child('items0'),
248 | bindVar1: wilddogRef.child('items1')
249 | },
250 | template:
251 | '' +
252 | '
{{ item[".key"] }} {{ item.index }}
' +
253 | '
{{ item[".key"] }} {{ item[".value"] }}
' +
254 | '
'
255 | }).$mount()
256 | wilddogRef.set({
257 | items0: {
258 | first: { index: 0 },
259 | second: { index: 1 },
260 | third: { index: 2 }
261 | },
262 | items1: ['first', 'second', 'third']
263 | }, function () {
264 | expect(vm.bindVar0).to.deep.equal([
265 | { '.key': 'first', index: 0 },
266 | { '.key': 'second', index: 1 },
267 | { '.key': 'third', index: 2 }
268 | ])
269 |
270 | expect(vm.bindVar1).to.deep.equal([
271 | { '.key': '0', '.value': 'first' },
272 | { '.key': '1', '.value': 'second' },
273 | { '.key': '2', '.value': 'third' }
274 | ])
275 | Vue.nextTick(function () {
276 | expect(vm.$el.textContent).to.contain('first 0 second 1 third 2')
277 | expect(vm.$el.textContent).to.contain('0 first 1 second 2 third')
278 | done()
279 | })
280 | })
281 | })
282 |
283 | it('updates an array record when its value changes', function (done) {
284 | var vm = new Vue({
285 | wilddog: {
286 | items: wilddogRef
287 | },
288 | template: '{{ item[".key"] }} {{ item[".value"] || item.foo }}
'
289 | }).$mount()
290 | wilddogRef.set({ a: 1, b: 2, c: 3 }, function () {
291 | wilddogRef.child('b').set({ foo: 'bar' }, function () {
292 | expect(vm.items).to.deep.equal([
293 | { '.key': 'a', '.value': 1 },
294 | { '.key': 'b', foo: 'bar' },
295 | { '.key': 'c', '.value': 3 }
296 | ])
297 | Vue.nextTick(function () {
298 | expect(vm.$el.textContent).to.contain('a 1 b bar c 3')
299 | done()
300 | })
301 | })
302 | })
303 | })
304 |
305 | it('removes an array record when it is deleted', function (done) {
306 | var vm = new Vue({
307 | wilddog: {
308 | items: wilddogRef
309 | },
310 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
311 | }).$mount()
312 | wilddogRef.set({ a: 1, b: 2, c: 3 }, function () {
313 | wilddogRef.child('b').remove(function () {
314 | expect(vm.items).to.deep.equal([
315 | { '.key': 'a', '.value': 1 },
316 | { '.key': 'c', '.value': 3 }
317 | ])
318 | Vue.nextTick(function () {
319 | expect(vm.$el.textContent).to.contain('a 1 c 3')
320 | done()
321 | })
322 | })
323 | })
324 | })
325 |
326 | it('moves an array record when it\'s order changes (moved to start of array) [orderByValue()]', function (done) {
327 | var vm = new Vue({
328 | wilddog: {
329 | items: wilddogRef.orderByValue()
330 | },
331 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
332 | }).$mount()
333 | wilddogRef.set({ a: 2, b: 3, c: 2 }, function () {
334 | wilddogRef.child('b').set(1, function () {
335 | expect(vm.items).to.deep.equal([
336 | { '.key': 'b', '.value': 1 },
337 | { '.key': 'a', '.value': 2 },
338 | { '.key': 'c', '.value': 2 }
339 | ])
340 | Vue.nextTick(function () {
341 | expect(vm.$el.textContent).to.contain('b 1 a 2 c 2')
342 | done()
343 | })
344 | })
345 | })
346 | })
347 |
348 | it('moves an array record when it\'s order changes (moved to middle of array) [orderByValue()]', function (done) {
349 | var vm = new Vue({
350 | wilddog: {
351 | items: wilddogRef.orderByValue()
352 | },
353 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
354 | }).$mount()
355 | wilddogRef.set({ a: 2, b: 1, c: 4 }, function () {
356 | wilddogRef.child('b').set(3, function () {
357 | expect(vm.items).to.deep.equal([
358 | { '.key': 'a', '.value': 2 },
359 | { '.key': 'b', '.value': 3 },
360 | { '.key': 'c', '.value': 4 }
361 | ])
362 | Vue.nextTick(function () {
363 | expect(vm.$el.textContent).to.contain('a 2 b 3 c 4')
364 | done()
365 | })
366 | })
367 | })
368 | })
369 |
370 | it('moves an array record when it\'s order changes (moved to end of array) [orderByValue()]', function (done) {
371 | var vm = new Vue({
372 | wilddog: {
373 | items: wilddogRef.orderByValue()
374 | },
375 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
376 | }).$mount()
377 | wilddogRef.set({ a: 2, b: 1, c: 3 }, function () {
378 | wilddogRef.child('b').set(4, function () {
379 | expect(vm.items).to.deep.equal([
380 | { '.key': 'a', '.value': 2 },
381 | { '.key': 'c', '.value': 3 },
382 | { '.key': 'b', '.value': 4 }
383 | ])
384 | Vue.nextTick(function () {
385 | expect(vm.$el.textContent).to.contain('a 2 c 3 b 4')
386 | done()
387 | })
388 | })
389 | })
390 | })
391 |
392 | it('moves an array record when it\'s order changes (moved to start of array) [orderByChild()]', function (done) {
393 | var vm = new Vue({
394 | wilddog: {
395 | items: wilddogRef.orderByChild('value')
396 | },
397 | template: '{{ item[".key"] }} {{ item.value }}
'
398 | }).$mount()
399 | wilddogRef.set({
400 | a: { value: 2 },
401 | b: { value: 3 },
402 | c: { value: 2 }
403 | }, function () {
404 | wilddogRef.child('b').set({ value: 1 }, function () {
405 | expect(vm.items).to.deep.equal([
406 | { '.key': 'b', value: 1 },
407 | { '.key': 'a', value: 2 },
408 | { '.key': 'c', value: 2 }
409 | ])
410 | Vue.nextTick(function () {
411 | expect(vm.$el.textContent).to.contain('b 1 a 2 c 2')
412 | done()
413 | })
414 | })
415 | })
416 | })
417 |
418 | it('moves an array record when it\'s order changes (moved to middle of array) [orderByChild()]', function (done) {
419 | var vm = new Vue({
420 | wilddog: {
421 | items: wilddogRef.orderByChild('value')
422 | },
423 | template: '{{ item[".key"] }} {{ item.value }}
'
424 | }).$mount()
425 | wilddogRef.set({
426 | a: { value: 2 },
427 | b: { value: 1 },
428 | c: { value: 4 }
429 | }, function () {
430 | wilddogRef.child('b').set({ value: 3 }, function () {
431 | expect(vm.items).to.deep.equal([
432 | { '.key': 'a', value: 2 },
433 | { '.key': 'b', value: 3 },
434 | { '.key': 'c', value: 4 }
435 | ])
436 | Vue.nextTick(function () {
437 | expect(vm.$el.textContent).to.contain('a 2 b 3 c 4')
438 | done()
439 | })
440 | })
441 | })
442 | })
443 |
444 | it('moves an array record when it\'s order changes (moved to end of array) [orderByChild()]', function (done) {
445 | var vm = new Vue({
446 | wilddog: {
447 | items: wilddogRef.orderByChild('value')
448 | },
449 | template: '{{ item[".key"] }} {{ item.value }}
'
450 | }).$mount()
451 | wilddogRef.set({
452 | a: { value: 2 },
453 | b: { value: 1 },
454 | c: { value: 3 }
455 | }, function () {
456 | wilddogRef.child('b').set({ value: 4 }, function () {
457 | expect(vm.items).to.deep.equal([
458 | { '.key': 'a', value: 2 },
459 | { '.key': 'c', value: 3 },
460 | { '.key': 'b', value: 4 }
461 | ])
462 | Vue.nextTick(function () {
463 | expect(vm.$el.textContent).to.contain('a 2 c 3 b 4')
464 | done()
465 | })
466 | })
467 | })
468 | })
469 |
470 | it('works with orderByKey() queries', function (done) {
471 | var vm = new Vue({
472 | wilddog: {
473 | items: wilddogRef.orderByKey()
474 | },
475 | template: '{{ item[".key"] }} {{ item[".value"] }}
'
476 | }).$mount()
477 | wilddogRef.set({ b: 2, c: 1, d: 3 }, function () {
478 | wilddogRef.update({ a: 4, d: 4, e: 0 }, function () {
479 | expect(vm.items).to.deep.equal([
480 | { '.key': 'a', '.value': 4 },
481 | { '.key': 'b', '.value': 2 },
482 | { '.key': 'c', '.value': 1 },
483 | { '.key': 'd', '.value': 4 },
484 | { '.key': 'e', '.value': 0 }
485 | ])
486 | Vue.nextTick(function () {
487 | expect(vm.$el.textContent).to.contain('a 4 b 2 c 1 d 4 e 0')
488 | done()
489 | })
490 | })
491 | })
492 | })
493 | })
494 |
495 | describe('bind as Object', function () {
496 | /* it('throws error for invalid wilddog ref', function () {
497 | helpers.invalidWilddogRefs.forEach(function (ref) {
498 | expect(function () {
499 | new Vue({
500 | wilddog: {
501 | items: {
502 | source: ref,
503 | asObject: true
504 | }
505 | }
506 | })
507 | }).to.throw('WildVue: invalid Wilddog binding source.')
508 | })
509 | })
510 | */
511 | it('binds to an Object', function (done) {
512 | var obj = {
513 | first: { index: 0 },
514 | second: { index: 1 },
515 | third: { index: 2 }
516 | }
517 | var vm = new Vue({
518 | wilddog: {
519 | items: {
520 | source: wilddogRef.child('items'),
521 | asObject: true
522 | }
523 | },
524 | template: '{{ items | json }}
'
525 | }).$mount()
526 | wilddogRef.child('items').set(obj, function () {
527 | obj['.key'] = 'items'
528 | expect(vm.items).to.deep.equal(obj)
529 | Vue.nextTick(function () {
530 | expect(vm.$el.textContent).to.contain(JSON.stringify(obj, null, 2))
531 | done()
532 | })
533 | })
534 | })
535 |
536 | it('binds with $bindAsObject', function (done) {
537 | var obj = {
538 | first: { index: 0 },
539 | second: { index: 1 },
540 | third: { index: 2 }
541 | }
542 | var vm = new Vue({
543 | template: '{{ items | json }}
',
544 | created: function () {
545 | this.$bindAsObject('items', wilddogRef.child('items'))
546 | }
547 | }).$mount()
548 | wilddogRef.child('items').set(obj, function () {
549 | obj['.key'] = 'items'
550 | expect(vm.items).to.deep.equal(obj)
551 | Vue.nextTick(function () {
552 | expect(vm.$el.textContent).to.contain(JSON.stringify(obj, null, 2))
553 | done()
554 | })
555 | })
556 | })
557 |
558 | it('binds to a primitive', function (done) {
559 | var vm = new Vue({
560 | wilddog: {
561 | items: {
562 | source: wilddogRef.child('items'),
563 | asObject: true
564 | }
565 | },
566 | template: '{{ items | json }}
'
567 | }).$mount()
568 | wilddogRef.child('items').set('foo', function () {
569 | expect(vm.items).to.deep.equal({
570 | '.key': 'items',
571 | '.value': 'foo'
572 | })
573 | Vue.nextTick(function () {
574 | expect(vm.$el.textContent).to.contain(JSON.stringify(vm.items, null, 2))
575 | done()
576 | })
577 | })
578 | })
579 |
580 | it('binds to Wilddog reference with no data', function (done) {
581 | var vm = new Vue({
582 | wilddog: {
583 | items: {
584 | source: wilddogRef.child('items'),
585 | asObject: true
586 | }
587 | },
588 | template: '{{ items | json }}
'
589 | }).$mount()
590 | wilddogRef.child('items').set(null, function () {
591 | expect(vm.items).to.deep.equal({
592 | '.key': 'items',
593 | '.value': null
594 | })
595 | Vue.nextTick(function () {
596 | expect(vm.$el.textContent).to.contain(JSON.stringify(vm.items, null, 2))
597 | done()
598 | })
599 | })
600 | })
601 |
602 | it('sets the key as null when bound to the root of the database', function (done) {
603 | var rootRef = wilddogRef.root()
604 | var vm = new Vue({
605 | wilddog: {
606 | items: {
607 | source: rootRef,
608 | asObject: true
609 | }
610 | },
611 | template: '{{ items | json }}
'
612 | }).$mount()
613 | rootRef.set('foo', function () {
614 | expect(vm.items).to.deep.equal({
615 | '.key': null,
616 | '.value': 'foo'
617 | })
618 | Vue.nextTick(function () {
619 | expect(vm.$el.textContent).to.contain(JSON.stringify(vm.items, null, 2))
620 | done()
621 | })
622 | })
623 | })
624 |
625 | it('binds with limit queries', function (done) {
626 | var vm = new Vue({
627 | wilddog: {
628 | items: {
629 | source: wilddogRef.child('items').limitToLast(2),
630 | asObject: true
631 | }
632 | },
633 | template: '{{ items | json }}
'
634 | }).$mount()
635 | wilddogRef.child('items').set({
636 | first: { index: 0 },
637 | second: { index: 1 },
638 | third: { index: 2 }
639 | }, function () {
640 | expect(vm.items).to.deep.equal({
641 | '.key': 'items',
642 | second: { index: 1 },
643 | third: { index: 2 }
644 | })
645 | Vue.nextTick(function () {
646 | expect(vm.$el.textContent).to.contain(JSON.stringify(vm.items, null, 2))
647 | done()
648 | })
649 | })
650 | })
651 |
652 | it('binds multiple Wilddog references to state variables at the same time', function (done) {
653 | var vm = new Vue({
654 | wilddog: {
655 | bindVar0: {
656 | source: wilddogRef.child('items0'),
657 | asObject: true
658 | },
659 | bindVar1: {
660 | source: wilddogRef.child('items1'),
661 | asObject: true
662 | }
663 | },
664 | template: '{{ bindVar0 | json }} {{ bindVar1 | json }}
'
665 | }).$mount()
666 |
667 | var items0 = {
668 | first: { index: 0 },
669 | second: { index: 1 },
670 | third: { index: 2 }
671 | }
672 |
673 | var items1 = {
674 | bar: {
675 | foo: 'baz'
676 | },
677 | baz: true,
678 | foo: 100
679 | }
680 |
681 | wilddogRef.set({
682 | items0: items0,
683 | items1: items1
684 | }, function () {
685 | items0['.key'] = 'items0'
686 | expect(vm.bindVar0).to.deep.equal(items0)
687 | items1['.key'] = 'items1'
688 | expect(vm.bindVar1).to.deep.equal(items1)
689 | Vue.nextTick(function () {
690 | expect(vm.$el.textContent).to.contain(JSON.stringify(vm.bindVar0, null, 2))
691 | expect(vm.$el.textContent).to.contain(JSON.stringify(vm.bindVar1, null, 2))
692 | done()
693 | })
694 | })
695 | })
696 |
697 | it('binds a mixture of arrays and objects to state variables at the same time', function (done) {
698 | var vm = new Vue({
699 | wilddog: {
700 | bindVar0: {
701 | source: wilddogRef.child('items0'),
702 | asObject: true
703 | },
704 | bindVar1: {
705 | source: wilddogRef.child('items1'),
706 | asObject: false
707 | }
708 | },
709 | template: '{{ bindVar0 | json }} {{ bindVar1 | json }}
'
710 | }).$mount()
711 |
712 | var items0 = {
713 | first: { index: 0 },
714 | second: { index: 1 },
715 | third: { index: 2 }
716 | }
717 |
718 | var items1 = {
719 | bar: {
720 | foo: 'baz'
721 | },
722 | baz: true,
723 | foo: 100
724 | }
725 |
726 | wilddogRef.set({
727 | items0: items0,
728 | items1: items1
729 | }, function () {
730 | items0['.key'] = 'items0'
731 | expect(vm.bindVar0).to.deep.equal(items0)
732 | expect(vm.bindVar1).to.deep.equal([
733 | { '.key': 'bar', foo: 'baz' },
734 | { '.key': 'baz', '.value': true },
735 | { '.key': 'foo', '.value': 100 }
736 | ])
737 | Vue.nextTick(function () {
738 | expect(vm.$el.textContent).to.contain(JSON.stringify(vm.bindVar0, null, 2))
739 | expect(vm.$el.textContent).to.contain(JSON.stringify(vm.bindVar1, null, 2))
740 | done()
741 | })
742 | })
743 | })
744 | })
745 |
746 | describe('unbind', function () {
747 | it('throws error given unbound key', function () {
748 | var vm = new Vue()
749 | expect(function () {
750 | vm.$unbind('items')
751 | }).to.throw(/not bound to a Wilddog reference/)
752 | })
753 |
754 | it('should work properly for instances with no wilddog bindings', function () {
755 | expect(function () {
756 | var vm = new Vue()
757 | vm.$destroy()
758 | }).not.to.throw()
759 | })
760 |
761 | it('unbinds the state bound to Wilddog as an array', function (done) {
762 | var vm = new Vue({
763 | wilddog: {
764 | items: wilddogRef
765 | }
766 | })
767 | wilddogRef.set({
768 | first: { index: 0 },
769 | second: { index: 1 },
770 | third: { index: 2 }
771 | }, function () {
772 | vm.$unbind('items')
773 | expect(vm.items).to.be.null
774 | expect(vm.$wilddogRefs.items).to.be.null
775 | expect(vm._wilddogSources.items).to.be.null
776 | expect(vm._wilddogListeners.items).to.be.null
777 | done()
778 | })
779 | })
780 |
781 | it('unbinds the state bound to Wilddog as an object', function (done) {
782 | var vm = new Vue({
783 | wilddog: {
784 | items: {
785 | source: wilddogRef,
786 | asObject: true
787 | }
788 | }
789 | })
790 | wilddogRef.set({
791 | first: { index: 0 },
792 | second: { index: 1 },
793 | third: { index: 2 }
794 | }, function () {
795 | vm.$unbind('items')
796 | expect(vm.items).to.be.null
797 | expect(vm.$wilddogRefs.items).to.be.null
798 | expect(vm._wilddogSources.items).to.be.null
799 | expect(vm._wilddogListeners.items).to.be.null
800 | done()
801 | })
802 | })
803 |
804 | it('unbinds all bound state when the component unmounts', function (done) {
805 | var vm = new Vue({
806 | wilddog: {
807 | item0: wilddogRef,
808 | item1: {
809 | source: wilddogRef,
810 | asObject: true
811 | }
812 | }
813 | })
814 | sinon.spy(vm, '$unbind')
815 | wilddogRef.set({
816 | first: { index: 0 },
817 | second: { index: 1 },
818 | third: { index: 2 }
819 | }, function () {
820 | vm.$destroy()
821 | expect(vm.$unbind).to.have.been.calledTwice
822 | expect(vm.item0).to.be.null
823 | expect(vm.item1).to.be.null
824 | done()
825 | })
826 | })
827 |
828 | it('handles already unbound state when the component unmounts', function (done) {
829 | var vm = new Vue({
830 | wilddog: {
831 | item0: wilddogRef,
832 | item1: {
833 | source: wilddogRef,
834 | asObject: true
835 | }
836 | }
837 | })
838 | sinon.spy(vm, '$unbind')
839 | wilddogRef.set({
840 | first: { index: 0 },
841 | second: { index: 1 },
842 | third: { index: 2 }
843 | }, function () {
844 | vm.$unbind('item0')
845 | vm.$destroy()
846 | expect(vm.$unbind).to.have.been.calledTwice
847 | expect(vm.item0).to.be.null
848 | expect(vm.item1).to.be.null
849 | done()
850 | })
851 | })
852 | })
853 | })
854 |
--------------------------------------------------------------------------------