@performant-software/shared-components 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/README.md +0 -0
- package/build/index.js +2 -0
- package/build/index.js.map +1 -0
- package/build/main.css +11 -0
- package/index.js +1 -0
- package/package.json +36 -0
- package/src/api/Attachments.js +28 -0
- package/src/api/BaseService.js +127 -0
- package/src/api/BaseTransform.js +55 -0
- package/src/api/FormDataTransform.js +30 -0
- package/src/api/NestedAttributesTransform.js +63 -0
- package/src/components/EditContainer.css +0 -0
- package/src/components/EditContainer.js +448 -0
- package/src/components/GoogleAnalytics.css +0 -0
- package/src/components/GoogleAnalytics.js +118 -0
- package/src/components/GoogleScript.js +5 -0
- package/src/components/InfiniteScroll.css +1 -0
- package/src/components/InfiniteScroll.js +120 -0
- package/src/components/Keyboard.css +11 -0
- package/src/components/Keyboard.js +55 -0
- package/src/i18n/en.json +204 -0
- package/src/i18n/i18n.js +24 -0
- package/src/index.js +34 -0
- package/src/utils/Browser.js +8 -0
- package/src/utils/Calendar.js +232 -0
- package/src/utils/Date.js +10 -0
- package/src/utils/DragDrop.js +17 -0
- package/src/utils/Element.js +36 -0
- package/src/utils/Map.js +27 -0
- package/src/utils/Object.js +114 -0
- package/src/utils/String.js +20 -0
- package/src/utils/Timer.js +32 -0
- package/src/utils/Utility.js +14 -0
- package/test/api/Attachments.spec.js +32 -0
- package/types/api/Attachments.js.flow +28 -0
- package/types/api/BaseService.js.flow +127 -0
- package/types/api/BaseTransform.js.flow +55 -0
- package/types/api/FormDataTransform.js.flow +30 -0
- package/types/api/NestedAttributesTransform.js.flow +63 -0
- package/types/components/EditContainer.js.flow +448 -0
- package/types/components/GoogleAnalytics.js.flow +118 -0
- package/types/components/GoogleScript.js.flow +5 -0
- package/types/components/InfiniteScroll.js.flow +120 -0
- package/types/components/Keyboard.js.flow +55 -0
- package/types/i18n/i18n.js.flow +24 -0
- package/types/index.js.flow +34 -0
- package/types/utils/Browser.js.flow +8 -0
- package/types/utils/Calendar.js.flow +232 -0
- package/types/utils/Date.js.flow +10 -0
- package/types/utils/DragDrop.js.flow +17 -0
- package/types/utils/Element.js.flow +36 -0
- package/types/utils/Map.js.flow +27 -0
- package/types/utils/Object.js.flow +114 -0
- package/types/utils/String.js.flow +20 -0
- package/types/utils/Timer.js.flow +32 -0
- package/types/utils/Utility.js.flow +14 -0
- package/webpack.config.js +3 -0
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import _ from 'underscore';
|
|
4
|
+
|
|
5
|
+
type OptionsProps = {
|
|
6
|
+
emptyValues: Array<any>,
|
|
7
|
+
ignoreHtml: boolean
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
const EMPTY_VALUES = [
|
|
11
|
+
'',
|
|
12
|
+
null,
|
|
13
|
+
undefined,
|
|
14
|
+
[],
|
|
15
|
+
{}
|
|
16
|
+
];
|
|
17
|
+
|
|
18
|
+
const DEFAULT_OPTIONS = {
|
|
19
|
+
emptyValues: EMPTY_VALUES,
|
|
20
|
+
ignoreHtml: true,
|
|
21
|
+
ignoreWhitespace: true
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const HTML_REGEX = /(<([^>]+)>)/gi;
|
|
25
|
+
const WHITESPACE_REGEX = /\s\s+/g;
|
|
26
|
+
|
|
27
|
+
/**
|
|
28
|
+
* Returns true if the passed value is considered "empty".
|
|
29
|
+
*
|
|
30
|
+
* @param value
|
|
31
|
+
*
|
|
32
|
+
* @returns {boolean|*}
|
|
33
|
+
*/
|
|
34
|
+
export const isEmpty = (value: any) => {
|
|
35
|
+
// If the value is an object or array, use underscore's isEmpty check.
|
|
36
|
+
if (_.isObject(value) || _.isArray(value)) {
|
|
37
|
+
return _.isEmpty(value);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
return !value;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Returns true if the passed two arguments as deep equal. This function will perform a recursive check against all of
|
|
45
|
+
* the keys in the objects.
|
|
46
|
+
*
|
|
47
|
+
* @param a
|
|
48
|
+
* @param b
|
|
49
|
+
*
|
|
50
|
+
* @returns {boolean}
|
|
51
|
+
*/
|
|
52
|
+
export const isEqual = (a: any, b: any, userOptions: OptionsProps = {}) => {
|
|
53
|
+
const options = _.defaults(userOptions, DEFAULT_OPTIONS);
|
|
54
|
+
|
|
55
|
+
// Check equality, consider empty strings and "null" values as equal
|
|
56
|
+
if (a === b || (_.contains(options.emptyValues, a) && _.contains(options.emptyValues, b))) {
|
|
57
|
+
return true;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Deep string comparison
|
|
61
|
+
if (_.isString(a) && _.isString(b)) {
|
|
62
|
+
let aString = a;
|
|
63
|
+
let bString = b;
|
|
64
|
+
|
|
65
|
+
// Remove superfluous whitespace
|
|
66
|
+
if (options.ignoreWhitespace) {
|
|
67
|
+
aString = a.replace(WHITESPACE_REGEX, ' ');
|
|
68
|
+
bString = b.replace(WHITESPACE_REGEX, ' ');
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
// If we're ignoring HTML, compare the string values with HTML tags removed
|
|
72
|
+
if (options.ignoreHtml) {
|
|
73
|
+
aString = aString.replace(HTML_REGEX, '');
|
|
74
|
+
bString = bString.replace(HTML_REGEX, '');
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
if (aString === bString) {
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
if (a !== null && typeof a === 'object' && b !== null && typeof b === 'object') {
|
|
83
|
+
const aKeys = _.keys(a);
|
|
84
|
+
const bKeys = _.keys(b);
|
|
85
|
+
|
|
86
|
+
// If the objects contain different number of keys, return false
|
|
87
|
+
if (aKeys.length !== bKeys.length) {
|
|
88
|
+
return false;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Recursively check each key for equality
|
|
92
|
+
let equal = true;
|
|
93
|
+
|
|
94
|
+
_.each(_.keys(a), (key) => {
|
|
95
|
+
if (!(_.has(b, key) && isEqual(a[key], b[key]))) {
|
|
96
|
+
equal = false;
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
// If any of the recursive keys are not equal, return false
|
|
101
|
+
if (!equal) {
|
|
102
|
+
return false;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// If we've made it this far, we've checked the equality of all the keys in both objects, return true
|
|
106
|
+
return true;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
return false;
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
export default {
|
|
113
|
+
|
|
114
|
+
};
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import _ from 'underscore';
|
|
4
|
+
|
|
5
|
+
const includes = (a: any, b: any) => (
|
|
6
|
+
a && b && a.toString().toLowerCase().includes(b.toString().toLowerCase())
|
|
7
|
+
);
|
|
8
|
+
|
|
9
|
+
const toString = (value: any) => {
|
|
10
|
+
if (_.isNumber(value) || _.isBoolean(value)) {
|
|
11
|
+
return value;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
return value || '';
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
export default {
|
|
18
|
+
includes,
|
|
19
|
+
toString
|
|
20
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
const DEFAULT_TIMEOUT = 500;
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* The timer class encapsulates the logic for setting and clearing a timeout. This is particularly useful for
|
|
7
|
+
* keydown/keyup events when we only want to perform an action after the user has finished typing.
|
|
8
|
+
*/
|
|
9
|
+
class Timer {
|
|
10
|
+
timeout: TimeoutID | null;
|
|
11
|
+
|
|
12
|
+
constructor() {
|
|
13
|
+
this.timeout = null;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Clears the search timer.
|
|
18
|
+
*/
|
|
19
|
+
clearSearchTimer() {
|
|
20
|
+
clearTimeout(this.timeout);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Sets the search timer.
|
|
25
|
+
*/
|
|
26
|
+
setSearchTimer(onTimeout: () => void) {
|
|
27
|
+
clearTimeout(this.timeout);
|
|
28
|
+
this.timeout = setTimeout(onTimeout, DEFAULT_TIMEOUT);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export default new Timer();
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Returns true if passed value is a promise (i.e, has a .then method)
|
|
5
|
+
*
|
|
6
|
+
* @param *
|
|
7
|
+
*
|
|
8
|
+
* @returns {boolean}
|
|
9
|
+
*/
|
|
10
|
+
const isPromise = (value: any) => !!value && typeof value === 'object' && typeof value.then === 'function';
|
|
11
|
+
|
|
12
|
+
export default {
|
|
13
|
+
isPromise
|
|
14
|
+
};
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import Attachments from '../../src/api/Attachments';
|
|
2
|
+
|
|
3
|
+
describe('toPayload', () => {
|
|
4
|
+
test('toPayload adds the attachment, if provided', () => {
|
|
5
|
+
const formData = {
|
|
6
|
+
append: jest.fn()
|
|
7
|
+
};
|
|
8
|
+
|
|
9
|
+
const record = {
|
|
10
|
+
attachment: jest.fn(),
|
|
11
|
+
attachment_remove: true
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
Attachments.toPayload(formData, 'test', record, 'attachment');
|
|
15
|
+
|
|
16
|
+
expect(formData.append).toHaveBeenCalledWith('test[attachment]', record.attachment);
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test('toPayload removes the attachment if the *_remove attribute is provided with no attachment', () => {
|
|
20
|
+
const formData = {
|
|
21
|
+
append: jest.fn()
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
const record = {
|
|
25
|
+
attachment_remove: true
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
Attachments.toPayload(formData, 'test', record, 'attachment');
|
|
29
|
+
|
|
30
|
+
expect(formData.append).toHaveBeenCalledWith('test[attachment_remove]', true);
|
|
31
|
+
});
|
|
32
|
+
});
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Helper class for handling binary data. This class should be used in conjunction with the FormDataTransform.
|
|
5
|
+
*/
|
|
6
|
+
class Attachments {
|
|
7
|
+
/**
|
|
8
|
+
* Appends the attachment for the passed record to the passed form data. If the attachment is not present, the
|
|
9
|
+
* "*_remove" attribute will be appended if the attachment has been removed.
|
|
10
|
+
*
|
|
11
|
+
* @param formData
|
|
12
|
+
* @param prefix
|
|
13
|
+
* @param record
|
|
14
|
+
* @param name
|
|
15
|
+
*/
|
|
16
|
+
toPayload(formData: FormData, prefix: string, record: any, name: string) {
|
|
17
|
+
const attachment = record[name];
|
|
18
|
+
const removeAttribute = `${name}_remove`;
|
|
19
|
+
|
|
20
|
+
if (attachment) {
|
|
21
|
+
formData.append(`${prefix}[${name}]`, attachment);
|
|
22
|
+
} else if (record[removeAttribute]) {
|
|
23
|
+
formData.append(`${prefix}[${removeAttribute}]`, record[removeAttribute]);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export default new Attachments();
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import axios, { type AxiosResponse } from 'axios';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Base class for making API calls. This class uses Axios under the hood and a customizable transform class for
|
|
7
|
+
* PUT/POST requests.
|
|
8
|
+
*/
|
|
9
|
+
class BaseService {
|
|
10
|
+
/**
|
|
11
|
+
* Constructs a new BaseService object. This constructor should never be used directly.
|
|
12
|
+
*/
|
|
13
|
+
constructor() {
|
|
14
|
+
if (this.constructor === BaseService) {
|
|
15
|
+
throw new TypeError('Abstract class "BaseService" cannot be instantiated directly.');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Calls the POST /api/<resource>/ endpoint with the passed item.
|
|
21
|
+
*
|
|
22
|
+
* @param item
|
|
23
|
+
*
|
|
24
|
+
* @returns {Promise<AxiosResponse<T>>}
|
|
25
|
+
*/
|
|
26
|
+
create(item: any): Promise<AxiosResponse> {
|
|
27
|
+
const transform = this.getTransform();
|
|
28
|
+
|
|
29
|
+
// $FlowFixMe - Flow doesn't currently support abstract classes
|
|
30
|
+
return axios.post(this.getBaseUrl(), transform.toPayload(item), this.getConfig());
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Calls the DELETE /api/<resource>/:id endpoint for the passed item.
|
|
35
|
+
*
|
|
36
|
+
* @param item
|
|
37
|
+
*
|
|
38
|
+
* @returns {Promise<AxiosResponse<T>>}
|
|
39
|
+
*/
|
|
40
|
+
delete(item: any) {
|
|
41
|
+
return axios.delete(`${this.getBaseUrl()}/${item.id}`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Calls the GET /api/<resource>/ endpoint.
|
|
46
|
+
*
|
|
47
|
+
* @returns {Promise<AxiosResponse<T>>}
|
|
48
|
+
*/
|
|
49
|
+
fetchAll(params: any) {
|
|
50
|
+
return axios.get(this.getBaseUrl(), { params });
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
/**
|
|
54
|
+
* Calls the GET /api/<resource>/:id endpoint.
|
|
55
|
+
*
|
|
56
|
+
* @returns {Promise<AxiosResponse<T>>}
|
|
57
|
+
*/
|
|
58
|
+
fetchOne(id: number) {
|
|
59
|
+
return axios.get(`${this.getBaseUrl()}/${id}`);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Calls the create/update API endpoint for the passed item.
|
|
64
|
+
*
|
|
65
|
+
* @param item
|
|
66
|
+
*
|
|
67
|
+
* @returns {Promise<AxiosResponse<T>>}
|
|
68
|
+
*/
|
|
69
|
+
save(item: any) {
|
|
70
|
+
return item.id ? this.update(item) : this.create(item);
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Calls the POST /api/<resource>/search endpoint.
|
|
75
|
+
*
|
|
76
|
+
* @param params
|
|
77
|
+
*
|
|
78
|
+
* @returns {Promise<AxiosResponse<T>>}
|
|
79
|
+
*/
|
|
80
|
+
search(params: any) {
|
|
81
|
+
return axios.post(`${this.getBaseUrl()}/search`, params);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* Calls the PUT /api/<resource>/:id endpoint with the passed item.
|
|
86
|
+
*
|
|
87
|
+
* @param item
|
|
88
|
+
*
|
|
89
|
+
* @returns {Promise<AxiosResponse<T>>}
|
|
90
|
+
*/
|
|
91
|
+
update(item: any) {
|
|
92
|
+
const transform = this.getTransform();
|
|
93
|
+
|
|
94
|
+
// $FlowFixMe - Flow doesn't currently support abstract classes
|
|
95
|
+
return axios.put(`${this.getBaseUrl()}/${item.id}`, transform.toPayload(item), this.getConfig());
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
// protected
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* Returns the API base URL string.
|
|
102
|
+
*/
|
|
103
|
+
getBaseUrl(): string {
|
|
104
|
+
// Implemented in concrete classes.
|
|
105
|
+
return '';
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Returns the config properties for POST/PUT requests.
|
|
110
|
+
*
|
|
111
|
+
* @returns {null}
|
|
112
|
+
*/
|
|
113
|
+
getConfig() {
|
|
114
|
+
// Implemented in concrete classes
|
|
115
|
+
return null;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
/**
|
|
119
|
+
* Returns the transform object. This class will be used to generate the object sent on POST/PUT requests.
|
|
120
|
+
*/
|
|
121
|
+
getTransform() {
|
|
122
|
+
// Implemented in concrete classes.
|
|
123
|
+
return {};
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export default BaseService;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import _ from 'underscore';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Class for handling transforming objects for PUT/POST requests. This class transforms records into
|
|
7
|
+
* plain Javascript objects.
|
|
8
|
+
*/
|
|
9
|
+
class BaseTransform {
|
|
10
|
+
/**
|
|
11
|
+
* Constructs a new BaseTransform object. This constructor should never be used directly.
|
|
12
|
+
*/
|
|
13
|
+
constructor() {
|
|
14
|
+
if (this.constructor === BaseTransform) {
|
|
15
|
+
throw new TypeError('Abstract class "BaseTransform" cannot be instantiated directly.');
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
// protected
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns the parameter name.
|
|
23
|
+
*
|
|
24
|
+
* @returns {string}
|
|
25
|
+
*/
|
|
26
|
+
getParameterName(): string {
|
|
27
|
+
// Implemented in sub-class
|
|
28
|
+
return '';
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Returns the array of payload keys.
|
|
33
|
+
*
|
|
34
|
+
* @returns {*[]}
|
|
35
|
+
*/
|
|
36
|
+
getPayloadKeys(): Array<string> {
|
|
37
|
+
// Implemented in sub-class
|
|
38
|
+
return [];
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
/**
|
|
42
|
+
* Returns the object for POST/PUT requests as a plain Javascript object.
|
|
43
|
+
*
|
|
44
|
+
* @param item
|
|
45
|
+
*
|
|
46
|
+
* @returns any
|
|
47
|
+
*/
|
|
48
|
+
toPayload(item: any): any {
|
|
49
|
+
return {
|
|
50
|
+
[this.getParameterName()]: _.pick(item, this.getPayloadKeys())
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
export default BaseTransform;
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import _ from 'underscore';
|
|
4
|
+
import BaseTransform from './BaseTransform';
|
|
5
|
+
import StringUtils from '../utils/String';
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* Class for handling transforming records for PUT/POST requests. This class transforms objects in FormData. This
|
|
9
|
+
* class is useful if your model contains binary data to be uploaded.
|
|
10
|
+
*/
|
|
11
|
+
class FormDataTransform extends BaseTransform {
|
|
12
|
+
/**
|
|
13
|
+
* Converts the passed records to a formData object to be sent on PUT/POST requests.
|
|
14
|
+
*
|
|
15
|
+
* @param record
|
|
16
|
+
*
|
|
17
|
+
* @returns {FormData}
|
|
18
|
+
*/
|
|
19
|
+
toPayload(record: any) {
|
|
20
|
+
const formData = new FormData();
|
|
21
|
+
|
|
22
|
+
_.each(this.getPayloadKeys(), (key) => {
|
|
23
|
+
formData.append(`${this.getParameterName()}[${key}]`, StringUtils.toString(record[key]));
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
return formData;
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export default FormDataTransform;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
// @flow
|
|
2
|
+
|
|
3
|
+
import _ from 'underscore';
|
|
4
|
+
import StringUtils from '../utils/String';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Class for handling transforming nested attributes of a parent object. This class will handle transforming the
|
|
8
|
+
* object into a payload to be sent to a POST/PUT request on an API. This class currently supports transforming into
|
|
9
|
+
* a plain Javascript object or a FormData object.
|
|
10
|
+
*/
|
|
11
|
+
class NestedAttributesTransform {
|
|
12
|
+
/**
|
|
13
|
+
* Constructs a new BaseTransform object. This constructor should never be used directly.
|
|
14
|
+
*/
|
|
15
|
+
constructor() {
|
|
16
|
+
if (this.constructor === NestedAttributesTransform) {
|
|
17
|
+
throw new TypeError('Abstract class "NestedAttributesTransform" cannot be instantiated directly.');
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Returns the array of payload keys.
|
|
23
|
+
*
|
|
24
|
+
* @returns {*[]}
|
|
25
|
+
*/
|
|
26
|
+
getPayloadKeys() {
|
|
27
|
+
// Implemented in sub-class.
|
|
28
|
+
return [];
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Appends the passed record's collection to the form data.
|
|
33
|
+
*
|
|
34
|
+
* @param formData
|
|
35
|
+
* @param prefix
|
|
36
|
+
* @param record
|
|
37
|
+
* @param collection
|
|
38
|
+
*/
|
|
39
|
+
toFormData(formData: FormData, prefix: string, record: any, collection: string) {
|
|
40
|
+
_.each(record[collection], (item, index) => {
|
|
41
|
+
_.each(this.getPayloadKeys(), (key) => {
|
|
42
|
+
formData.append(`${prefix}[${collection}][${index}][${key}]`, StringUtils.toString(item[key]));
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Transforms the passed record's collection to a payload object.
|
|
49
|
+
*
|
|
50
|
+
* @param record
|
|
51
|
+
* @param collection
|
|
52
|
+
*
|
|
53
|
+
* @returns {{[p: string]: *}}
|
|
54
|
+
*/
|
|
55
|
+
toPayload(record: any, collection: string) {
|
|
56
|
+
return {
|
|
57
|
+
[collection]: _.map(record[collection],
|
|
58
|
+
(item, index) => ({ ..._.pick(item, this.getPayloadKeys()), order: index }))
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export default NestedAttributesTransform;
|