Skip to content

Commit f4e1118

Browse files
committed
enforcing strict lodash path strings, readme update
1 parent 77c0e73 commit f4e1118

File tree

8 files changed

+185
-109
lines changed

8 files changed

+185
-109
lines changed

.eslintignore

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,6 @@
11
dist
22
archive
3-
test
3+
test
4+
index.js
5+
vue-deepset.js
6+
vue-deepset.min.js

README.md

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
# vue-deepset
2-
Deep set Vue.js objects
2+
Deep set Vue.js objects using dynamic paths
33

44
---
55

66
Binding deeply nested data properties and vuex data to a form or component can be tricky. The following set of tools aims to simplify data bindings. Compatible with `Vue 1.x`, `Vue 2.x`, `Vuex 1.x`, and `Vuex 2.x`
77

8-
**Note** `vueModel` and `vuexModel` use [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) objects if supported by the browser and fallback to an object with generated fields based on the target object. Because of this, it is always best to pre-define the properties of an object.
8+
**Note** `vueModel` and `vuexModel` use [Proxy](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Proxy) objects if supported by the browser and fallback to an object with generated fields based on the target object. Because of this, it is always best to pre-define the properties of an object when using older browsers.
99

10-
Also note that models are flat and once built can set vue/vuex directly using `model[path] = value`
10+
Also note that models are flat and once built can set vue/vuex directly using `model[path] = value` where path is a lodash formatted path string or path array.
1111

1212
### Examples
1313

@@ -18,9 +18,41 @@ Full examples can be found in the [tests](https://github.com/bhoriuchi/vue-deeps
1818
* `vue@>=1.0.0`
1919
* `vuex@>=1.0.0` (optional)
2020

21+
### Dynamic paths
22+
23+
If you knew what every path you needed ahead of time you could (while tedious) create custom computed properties with getter and setter methods for each of those properties. But what if you have a dynamic and deeply nested property? This problem was actually what inspired the creation of this library. Using a Proxy, `vue-deepset` is able to dynamically create new, deep, reactive properties as well as return `undefined` for values that are not yet set.
24+
2125
### Path Strings
2226

23-
The modeling methods use `lodash.toPath` format for path strings. Please ensure references use this format
27+
The modeling methods use `lodash.toPath` format for path strings. Please ensure references use this format. You may also use `path Arrays` which can be easier to construct when using keys that include dots.
28+
29+
The following 2 path values are the same
30+
```js
31+
const stringPath = 'a.b["c.d"].e[0]'
32+
const arrayPath = [ 'a', 'b', 'c.d', 'e', 0 ]
33+
```
34+
35+
#### Keys with dots
36+
37+
Since dots prefix a nested path and are also valid characters for a key data that looks like the following can be tricky
38+
39+
```js
40+
const data = {
41+
'foo.bar': 'baz',
42+
foo: {
43+
bar: 'qux'
44+
}
45+
}
46+
```
47+
48+
So care should be taken when building the path string (or just use an array path) as the following will be true
49+
50+
```js
51+
'foo.bar' // qux
52+
'["foo.bar"]' // baz
53+
'foo["bar"]' // qux
54+
'["foo"].bar' // qux
55+
```
2456

2557
### Binding `v-model` to deeply nested objects
2658

@@ -35,7 +67,7 @@ Model objects returned by `$deepModel`, `vueModel`, and `vuexModel` are flat and
3567
## Usage
3668

3769
* Webpack `import * as VueDeepSet from 'vue-deepset'`
38-
* Browser `<script src='./node_modules/vue-deepset/vue-deepset.js'></script>`
70+
* Browser `<script src='./node_modules/vue-deepset/vue-deepset.min.js'></script>`
3971

4072
### As a Plugin
4173

@@ -212,9 +244,8 @@ import { vueSet } from 'vue-deepset'
212244

213245
export default {
214246
methods: {
215-
vueSet: vueSet,
216247
clearForm () {
217-
this.vueSet(this.localForm, 'message', '')
248+
vueSet(this.localForm, 'message', '')
218249
}
219250
},
220251
data: {

index.js

Lines changed: 42 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,11 @@ var rePropName = RegExp(
3737
// Or match "" as the space between consecutive dots or empty brackets.
3838
'(?=(?:\\.|\\[\\])(?:\\.|\\[\\]|$))', 'g');
3939

40-
// modified from lodash
40+
// modified from lodash - https://github.com/lodash/lodash
4141
function toPath(string) {
42+
if (Array.isArray(string)) {
43+
return string;
44+
}
4245
var result = [];
4346
if (string.charCodeAt(0) === charCodeOfDot) {
4447
result.push('');
@@ -57,6 +60,14 @@ function toPath(string) {
5760

5861
function noop() {}
5962

63+
function hasOwnProperty(object, property) {
64+
return Object.prototype.hasOwnProperty.call(object, property);
65+
}
66+
67+
function deepsetError(message) {
68+
return new Error('[vue-deepset]: ' + message);
69+
}
70+
6071
function isObjectLike(object) {
6172
return (typeof object === 'undefined' ? 'undefined' : _typeof(object)) === 'object' && object !== null;
6273
}
@@ -117,12 +128,11 @@ function getPaths(object) {
117128
pushPaths(val, (current + '.' + key).replace(/^\./, ''), paths);
118129
pushPaths(val, (current + '[' + key + ']').replace(/^\./, ''), paths);
119130
pushPaths(val, (current + '["' + key + '"]').replace(/^\./, ''), paths);
120-
} else if (key.match(invalidKey) !== null) {
121-
// must quote
122-
pushPaths(val, (current + '["' + key + '"]').replace(/^\./, ''), paths);
123-
} else {
131+
} else if (!key.match(invalidKey)) {
124132
pushPaths(val, (current + '.' + key).replace(/^\./, ''), paths);
125133
}
134+
// always add the absolute array notation path
135+
pushPaths(val, (current + '["' + key + '"]').replace(/^\./, ''), paths);
126136
});
127137
}
128138
return [].concat(new Set(paths));
@@ -145,22 +155,14 @@ function _get(obj, path, defaultValue) {
145155
return defaultValue;
146156
}
147157

148-
function hasOwnProperty(object, property) {
149-
return Object.prototype.hasOwnProperty.call(object, property);
150-
}
151-
152-
function getProp(object, property) {
153-
return Object.keys(object).indexOf(property) === -1 ? _get(object, property) : object[property];
154-
}
155-
156158
function getProxy(vm, base, options) {
157159
noop(options); // for future potential options
158160
var isVuex = typeof base === 'string';
159161
var object = isVuex ? _get(vm.$store.state, base) : base;
160162

161163
return new Proxy(object, {
162164
get: function get(target, property) {
163-
return getProp(target, property);
165+
return _get(target, property);
164166
},
165167
set: function set(target, property, value) {
166168
isVuex ? vuexSet.call(vm, pathJoin(base, property), value) : vueSet(target, property, value);
@@ -183,7 +185,7 @@ function getProxy(vm, base, options) {
183185
},
184186
getOwnPropertyDescriptor: function getOwnPropertyDescriptor(target, property) {
185187
return {
186-
value: getProp(target, property),
188+
value: _get(target, property),
187189
writable: false,
188190
enumerable: true,
189191
configurable: true
@@ -230,22 +232,30 @@ function buildVuexModel(vm, vuexPath, options) {
230232
}
231233

232234
function vueSet(object, path, value) {
233-
var parts = toPath(path);
234-
var obj = object;
235-
while (parts.length) {
236-
var key = parts.shift();
237-
if (!parts.length) {
238-
_vue2.default.set(obj, key, value);
239-
} else if (!hasOwnProperty(obj, key) || obj[key] === null) {
240-
_vue2.default.set(obj, key, typeof key === 'number' ? [] : {});
235+
try {
236+
var parts = toPath(path);
237+
var obj = object;
238+
while (parts.length) {
239+
var key = parts.shift();
240+
if (!parts.length) {
241+
_vue2.default.set(obj, key, value);
242+
} else if (!hasOwnProperty(obj, key) || obj[key] === null) {
243+
_vue2.default.set(obj, key, typeof key === 'number' ? [] : {});
244+
}
245+
obj = obj[key];
241246
}
242-
obj = obj[key];
247+
return object;
248+
} catch (err) {
249+
throw deepsetError('vueSet unable to set object (' + err.message + ')');
243250
}
244251
}
245252

246253
function vuexSet(path, value) {
247-
if (!this.$store) throw new Error('[vue-deepset]: could not find vuex store object on instance');
248-
this.$store[this.$store.commit ? 'commit' : 'dispatch']('VUEX_DEEP_SET', { path: path, value: value });
254+
if (!isObjectLike(this.$store)) {
255+
throw deepsetError('could not find vuex store object on instance');
256+
}
257+
var method = this.$store.commit ? 'commit' : 'dispatch';
258+
this.$store[method]('VUEX_DEEP_SET', { path: path, value: value });
249259
}
250260

251261
function VUEX_DEEP_SET(state, _ref) {
@@ -264,7 +274,7 @@ function extendMutation() {
264274
function vueModel(object, options) {
265275
var opts = Object.assign({}, options);
266276
if (!isObjectLike(object)) {
267-
throw new Error('[vue-deepset]: invalid object specified for vue model');
277+
throw deepsetError('invalid object specified for vue model');
268278
} else if (opts.useProxy === false || typeof Proxy === 'undefined') {
269279
return buildVueModel(this, object, opts);
270280
}
@@ -274,9 +284,11 @@ function vueModel(object, options) {
274284
function vuexModel(vuexPath, options) {
275285
var opts = Object.assign({}, options);
276286
if (typeof vuexPath !== 'string' || vuexPath === '') {
277-
throw new Error('[vue-deepset]: invalid vuex path string');
287+
throw deepsetError('invalid vuex path string');
288+
} else if (!isObjectLike(this.$store) || !isObjectLike(this.$store.state)) {
289+
throw deepsetError('no vuex state found');
278290
} else if (!has(this.$store.state, vuexPath)) {
279-
throw new Error('[vue-deepset]: Cannot find path "' + vuexPath + '" in Vuex store');
291+
throw deepsetError('cannot find path "' + vuexPath + '" in Vuex store');
280292
} else if (opts.useProxy === false || typeof Proxy === 'undefined') {
281293
return buildVuexModel(this, vuexPath, opts);
282294
}
@@ -288,8 +300,7 @@ function deepModel(base, options) {
288300
}
289301

290302
function install(VueInstance) {
291-
VueInstance.prototype.$vueModel = vueModel;
292-
VueInstance.prototype.$vuexModel = vuexModel;
293303
VueInstance.prototype.$deepModel = deepModel;
294304
VueInstance.prototype.$vueSet = vueSet;
305+
VueInstance.prototype.$vuexSet = vuexSet;
295306
}

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "vue-deepset",
3-
"version": "0.6.0",
3+
"version": "0.6.1",
44
"description": "Deep set Vue.js objects",
55
"main": "index.js",
66
"scripts": {

0 commit comments

Comments
 (0)