@rentman/rm-core 1.0.0

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.
Files changed (68) hide show
  1. package/.editorconfig +23 -0
  2. package/package.json +33 -0
  3. package/src/classes/ApiFormatter.ts +338 -0
  4. package/src/classes/BatchStatus.ts +86 -0
  5. package/src/classes/ChangesCompiler.ts +161 -0
  6. package/src/classes/ChangesProcessor.ts +475 -0
  7. package/src/classes/CollectionCache.ts +234 -0
  8. package/src/classes/CollectionEntryService.ts +83 -0
  9. package/src/classes/CollectionHandler.ts +619 -0
  10. package/src/classes/CollectionResourceService.ts +38 -0
  11. package/src/classes/ColumnCache.ts +114 -0
  12. package/src/classes/ColumnExpansion.ts +74 -0
  13. package/src/classes/ColumnHandler.ts +90 -0
  14. package/src/classes/ColumnSet.ts +181 -0
  15. package/src/classes/ColumnUtil.ts +589 -0
  16. package/src/classes/CreateRequest.ts +158 -0
  17. package/src/classes/ItemAdministrator.ts +571 -0
  18. package/src/classes/ItemCache.ts +158 -0
  19. package/src/classes/ItemEntryService.ts +488 -0
  20. package/src/classes/ItemHandler.ts +592 -0
  21. package/src/classes/ItemRequestService.ts +229 -0
  22. package/src/classes/ItemResourceService.ts +36 -0
  23. package/src/classes/KeyService.ts +76 -0
  24. package/src/classes/LockService.ts +184 -0
  25. package/src/classes/ModuleChangesCompiler.ts +123 -0
  26. package/src/classes/PromiseService.ts +19 -0
  27. package/src/classes/QueryMatcher.ts +135 -0
  28. package/src/classes/QueryParser.ts +209 -0
  29. package/src/classes/QueryService.ts +35 -0
  30. package/src/classes/RequestColumnExpansion.ts +31 -0
  31. package/src/classes/ResourceCache.ts +289 -0
  32. package/src/classes/Util.ts +422 -0
  33. package/src/constants.ts +11 -0
  34. package/src/factories/BatchStatusFactory.ts +18 -0
  35. package/src/factories/ChangesCompilerFactory.ts +20 -0
  36. package/src/factories/ChangesProcessorFactory.ts +53 -0
  37. package/src/factories/CollectionCacheFactory.ts +15 -0
  38. package/src/factories/CollectionEntryFactory.ts +35 -0
  39. package/src/factories/CollectionResourceFactory.ts +36 -0
  40. package/src/factories/ColumnExpansionFactory.ts +25 -0
  41. package/src/factories/ColumnSetFactory.ts +24 -0
  42. package/src/factories/CreateRequestFactory.ts +28 -0
  43. package/src/factories/ItemCacheFactory.ts +18 -0
  44. package/src/factories/ItemEntryFactory.ts +18 -0
  45. package/src/factories/ItemResourceFactory.ts +28 -0
  46. package/src/factories/ModuleChangesCompilerFactory.ts +21 -0
  47. package/src/factories/QueryMatcherFactory.ts +20 -0
  48. package/src/factories/QueryParserFactory.ts +10 -0
  49. package/src/factories/RequestColumnExpansionFactory.ts +15 -0
  50. package/src/factories/ResourceCacheFactory.ts +55 -0
  51. package/src/index.ts +188 -0
  52. package/src/interfaces/ApiRequestServiceInterface.ts +17 -0
  53. package/src/interfaces/AuthInterface.ts +5 -0
  54. package/src/interfaces/ErrorHandlerInterface.ts +3 -0
  55. package/src/interfaces/ResourceCacheInterface.ts +9 -0
  56. package/src/models/CollectionEditResource.ts +139 -0
  57. package/src/models/CollectionEntry.ts +448 -0
  58. package/src/models/CollectionResource.ts +223 -0
  59. package/src/models/CollectionViewResource.ts +23 -0
  60. package/src/models/EditResource.ts +15 -0
  61. package/src/models/ItemEntry.ts +24 -0
  62. package/src/models/ItemResource.ts +35 -0
  63. package/src/models/ViewResource.ts +14 -0
  64. package/tsconfig.json +12 -0
  65. package/tslint.json +63 -0
  66. package/webpack.common.js +35 -0
  67. package/webpack.dev.js +10 -0
  68. package/webpack.prod.js +17 -0
@@ -0,0 +1,135 @@
1
+ import * as moment from 'moment';
2
+ import ApiFormatter from './ApiFormatter';
3
+ import { hasProperties, inArray } from './Util';
4
+
5
+ const MAYBE = 'maybe';
6
+
7
+ export default class QueryMatcher {
8
+ constructor (
9
+ private apiFormatter: ApiFormatter,
10
+ public parentType: string,
11
+ public parentId: number,
12
+ public fields: object,
13
+ public itemType?: string
14
+ ) {
15
+
16
+ }
17
+
18
+ private compareValues (value, queryValue) {
19
+ if (moment.isMoment(queryValue))
20
+ return this.apiFormatter.compareMomentObjects(queryValue, value);
21
+
22
+ if (moment.isDuration(queryValue))
23
+ return this.apiFormatter.compareDurations(queryValue, value);
24
+
25
+ return queryValue == value;
26
+ }
27
+
28
+ matchQuery (query: any): boolean | string {
29
+ if (query.search !== undefined)
30
+ return MAYBE;
31
+
32
+ if (query.custom !== undefined)
33
+ return MAYBE;
34
+
35
+ if (query.linkedTo !== undefined)
36
+ return this.matchLinkedToQuery(query);
37
+
38
+ if (query.conditions !== undefined)
39
+ return this.matchComplexQuery(query);
40
+
41
+ if (query.key !== undefined)
42
+ return this.matchSimpleQuery(this.fields[query.key], query);
43
+
44
+ if (!hasProperties(query))
45
+ return true;
46
+
47
+ throw new Error('queryMatcher: Unsupported query! Parsed?');
48
+ }
49
+
50
+ private matchLinkedToQuery (query) {
51
+ if (query.reverse)
52
+ return MAYBE;
53
+
54
+ let match;
55
+
56
+ if (query.linkedTo !== this.parentType)
57
+ match = false;
58
+ else
59
+ match = this.matchSimpleQuery(this.parentId, query.query);
60
+
61
+ if (match === MAYBE)
62
+ return MAYBE;
63
+
64
+ return query.not ? !match : match;
65
+ }
66
+
67
+ private matchComplexQuery (query) {
68
+ const conditions = query.conditions;
69
+
70
+ let result;
71
+ let terminal;
72
+
73
+ switch (query.operator) {
74
+ case 'AND':
75
+ result = true;
76
+ terminal = false;
77
+ break;
78
+ case 'OR':
79
+ result = false;
80
+ terminal = true;
81
+ break;
82
+ default:
83
+ throw new Error('queryMatcher: Unsupported operator');
84
+ }
85
+
86
+ for (const condition of conditions) {
87
+ const match = this.matchQuery(condition);
88
+
89
+ if (match === terminal)
90
+ return terminal;
91
+
92
+ if (match === MAYBE)
93
+ result = MAYBE;
94
+ }
95
+
96
+ return result;
97
+ }
98
+
99
+ private matchSimpleQuery (value, query) {
100
+ const key = query.key;
101
+ let queryValue = query.value;
102
+
103
+ if (!hasProperties(query)) // Could be linkedTo an empty object
104
+ return true;
105
+
106
+ if (value === undefined) {
107
+ console.warn('Required field', key, 'not found for', this.itemType);
108
+ return MAYBE;
109
+ }
110
+
111
+ if (queryValue === 'NOW')
112
+ queryValue = moment();
113
+
114
+ switch (query.comparator) {
115
+ case '=':
116
+ return this.compareValues(value, queryValue);
117
+ case '!=':
118
+ return !this.compareValues(value, queryValue);
119
+ case '<':
120
+ return value < queryValue;
121
+ case '<=':
122
+ return value <= queryValue;
123
+ case '>':
124
+ return value > queryValue;
125
+ case '>=':
126
+ return value >= queryValue;
127
+ case 'IN':
128
+ return inArray(value, queryValue);
129
+ case 'NOT IN':
130
+ return !inArray(value, queryValue);
131
+ default:
132
+ throw new Error('queryMatcher: Unsupported comparator: ' + query.comparator + ' in query ' + query);
133
+ }
134
+ }
135
+ }
@@ -0,0 +1,209 @@
1
+ import * as moment from 'moment';
2
+ import { getFirstProperty, hasProperties } from "./Util";
3
+
4
+ enum CONSTANTS {
5
+ OBJ = 0,
6
+ STR = 1,
7
+ SEP = ':'
8
+ }
9
+
10
+ export default class QueryParser {
11
+ query: any;
12
+ parameters: any;
13
+
14
+ constructor (query, parameters) {
15
+ this.query = query;
16
+ this.parameters = parameters;
17
+ }
18
+
19
+ private processValue (value) {
20
+ // algorithm is not important, as long as all arrays are sorted the same way
21
+ if (Array.isArray(value))
22
+ return value.slice().sort();
23
+
24
+ if (moment.isMoment(value))
25
+ return moment(value);
26
+
27
+ if (moment.isDuration(value))
28
+ return moment.duration(value);
29
+
30
+ return value;
31
+ }
32
+
33
+ private compareParsedQueries (a, b) {
34
+ if (a[CONSTANTS.STR] === b[CONSTANTS.STR])
35
+ return 0;
36
+
37
+ return a[CONSTANTS.STR] > b[CONSTANTS.STR] ? 1 : -1;
38
+ }
39
+
40
+ parse (query = this.query) {
41
+ if (query.linkedTo !== undefined)
42
+ return this.parseLinkedToQuery(
43
+ query.linkedTo,
44
+ query.query,
45
+ query.reverse,
46
+ query.not
47
+ );
48
+
49
+ if (query.not !== undefined)
50
+ throw new Error('queryParser: "Not" not supported');
51
+
52
+ if (query.custom !== undefined)
53
+ return this.parseCustomQuery(
54
+ query.custom,
55
+ query.parameters
56
+ );
57
+
58
+ if (query.search !== undefined)
59
+ return this.parseSearchQuery(query.search);
60
+
61
+ if (query.conditions !== undefined)
62
+ return this.parseComplexQuery(
63
+ query.conditions,
64
+ query.operator
65
+ );
66
+
67
+ if (query.key !== undefined)
68
+ return this.parseSimpleQuery(
69
+ query.key,
70
+ query.value,
71
+ query.comparator
72
+ );
73
+
74
+ if (!hasProperties(query))
75
+ return [{}, '{}'];
76
+
77
+ const key = getFirstProperty(query);
78
+
79
+ return this.parseSimpleQuery(key, query[key]);
80
+ }
81
+
82
+ private parseSearchQuery (value) {
83
+ value = this.formatValue(value);
84
+
85
+ return [
86
+ {search: value[CONSTANTS.OBJ]},
87
+ '[' + 'search' + CONSTANTS.SEP + value[CONSTANTS.STR] + ']'
88
+ ];
89
+ }
90
+
91
+ private parseCustomQuery (custom, parameters) {
92
+ if (parameters === undefined)
93
+ return [
94
+ {custom: custom},
95
+ '[' + custom + ']'
96
+ ];
97
+
98
+ const parsedParameters = this.parseCustomParameters(parameters);
99
+
100
+ return [
101
+ {custom: custom, parameters: parsedParameters[CONSTANTS.OBJ]},
102
+ '[' + custom + CONSTANTS.SEP + parsedParameters[CONSTANTS.STR] + ']'
103
+ ];
104
+ }
105
+
106
+ private parseCustomParameters (parameters) {
107
+ const keys = Object.keys(parameters).sort();
108
+
109
+ const query = {};
110
+ let string = '';
111
+
112
+ for (let i = 0; i < keys.length; i++) {
113
+ const key = keys[i];
114
+ const value = this.formatValue(parameters[key]);
115
+
116
+ query[key] = value[CONSTANTS.OBJ];
117
+
118
+ if (i > 0)
119
+ string += CONSTANTS.SEP;
120
+
121
+ string += value[CONSTANTS.STR];
122
+ }
123
+
124
+ return [query, string];
125
+ }
126
+
127
+ private parseSimpleQuery (key, value, comparator?) {
128
+ value = this.formatValue(value);
129
+
130
+ if (comparator === undefined)
131
+ comparator = Array.isArray(value[CONSTANTS.OBJ]) ? 'IN' : '=';
132
+
133
+ return [
134
+ {key: key, value: value[CONSTANTS.OBJ], comparator: comparator},
135
+ '[' + comparator + CONSTANTS.SEP + key + CONSTANTS.SEP + value[CONSTANTS.STR] + ']'
136
+ ];
137
+ }
138
+
139
+ private parseLinkedToQuery (linkedTo, query, reverse, not) {
140
+ if (query === undefined)
141
+ query = {};
142
+
143
+ if (reverse === undefined)
144
+ reverse = false;
145
+
146
+ if (not === undefined)
147
+ not = false;
148
+
149
+ const parsedQuery = this.parse(query);
150
+
151
+ return [
152
+ {linkedTo: linkedTo, query: parsedQuery[CONSTANTS.OBJ], reverse: reverse, not: not},
153
+ '[' + 'linkedTo' + CONSTANTS.SEP + linkedTo + CONSTANTS.SEP + parsedQuery[CONSTANTS.STR] + CONSTANTS.SEP + reverse + CONSTANTS.SEP + not + ']'
154
+ ];
155
+ }
156
+
157
+ private parseComplexQuery (conditions, operator) {
158
+ if (operator === undefined)
159
+ operator = 'AND';
160
+ else
161
+ operator = operator.toUpperCase();
162
+
163
+ const parsedConditions = this.parseConditions(conditions);
164
+
165
+ return [
166
+ {conditions: parsedConditions[CONSTANTS.OBJ], operator: operator},
167
+ '[' + operator + parsedConditions[CONSTANTS.STR] + ']'
168
+ ];
169
+ }
170
+
171
+ private parseConditions (conditions) {
172
+ let parsedConditions = [];
173
+ const query = [];
174
+ let string = '';
175
+
176
+ for (const condition of conditions)
177
+ parsedConditions.push(this.parse(condition));
178
+
179
+ parsedConditions = parsedConditions.sort(this.compareParsedQueries);
180
+
181
+ for (const parsedCondition of parsedConditions) {
182
+ query.push(parsedCondition[CONSTANTS.OBJ]);
183
+ string += parsedCondition[CONSTANTS.STR];
184
+ }
185
+
186
+ return [query, string];
187
+ }
188
+
189
+ private formatValue (value) {
190
+ let processedValue;
191
+
192
+ if (typeof value === 'string' && value.charAt(0) === '&') {
193
+ const key = value.substring(1);
194
+
195
+ value = this.parameters[key];
196
+
197
+ if (value === undefined)
198
+ throw new Error('queryParser: Undefined template parameter \'' + key + '\'');
199
+
200
+ processedValue = this.processValue(value);
201
+
202
+ return [processedValue, key + '>' + value];
203
+ }
204
+
205
+ processedValue = this.processValue(value);
206
+
207
+ return [processedValue, '' + processedValue];
208
+ }
209
+ }
@@ -0,0 +1,35 @@
1
+ export default class QueryService {
2
+ constructor () {
3
+ }
4
+
5
+ addAndCondition (query, condition) {
6
+ if (query.conditions === undefined || query.operator === 'or')
7
+ return {conditions: [query, condition]};
8
+
9
+ // we have an 'and' condition
10
+ return {conditions: query.conditions.concat(condition)};
11
+ }
12
+
13
+ addOrCondition (query, condition) {
14
+ if (query.operator === 'or')
15
+ return {conditions: query.conditions.concat(condition)};
16
+
17
+ // we have an 'and' condition
18
+ return {conditions: [query, condition], operator: 'or'};
19
+ }
20
+
21
+ addParentCondition (query, parent) {
22
+ let linkedToQuery;
23
+
24
+ if (parent === null)
25
+ return query;
26
+
27
+ linkedToQuery = {linkedTo: parent.itemType, query: parent.itemId};
28
+
29
+ return query === null ? linkedToQuery : this.addAndCondition(query, linkedToQuery);
30
+ }
31
+
32
+ createNotIn (idArray) {
33
+ return {comparator: 'NOT IN', key: 'id', value: idArray};
34
+ }
35
+ }
@@ -0,0 +1,31 @@
1
+ import ColumnUtil from "./ColumnUtil";
2
+
3
+ export default class RequestColumnExpansion {
4
+ private columns = {};
5
+ private columnInfoIdMap = {};
6
+
7
+ constructor (
8
+ private columnUtil: ColumnUtil
9
+ ) {
10
+ }
11
+
12
+ expand (columnInfo) {
13
+ if (this.columnInfoIdMap[columnInfo.id])
14
+ return;
15
+
16
+ const itemType = columnInfo.itemType;
17
+
18
+ if (this.columns[itemType] === undefined)
19
+ this.columns[itemType] = [];
20
+
21
+ this.columns[itemType] = this.columnUtil
22
+ .mergeRequestColumns(this.columns[itemType], columnInfo.request)
23
+ .merged;
24
+
25
+ this.columnInfoIdMap[columnInfo.id] = true;
26
+ }
27
+
28
+ get () {
29
+ return this.columns;
30
+ }
31
+ }
@@ -0,0 +1,289 @@
1
+ import ChangesProcessorFactory from "../factories/ChangesProcessorFactory";
2
+ import CollectionCacheFactory from "../factories/CollectionCacheFactory";
3
+ import CollectionResourceFactory from "../factories/CollectionResourceFactory";
4
+ import ItemCacheFactory from "../factories/ItemCacheFactory";
5
+ import ItemResourceFactory from "../factories/ItemResourceFactory";
6
+ import ApiRequestServiceInterface from '../interfaces/ApiRequestServiceInterface';
7
+ import ErrorHandlerInterface from '../interfaces/ErrorHandlerInterface';
8
+ import CollectionEntry from "../models/CollectionEntry";
9
+ import CollectionResource from "../models/CollectionResource";
10
+ import ItemEntry from "../models/ItemEntry";
11
+ import ItemResource from "../models/ItemResource";
12
+ import CollectionCache from "./CollectionCache";
13
+ import CollectionHandler from "./CollectionHandler";
14
+ import ColumnHandler from "./ColumnHandler";
15
+ import ItemCache from './ItemCache';
16
+ import ItemEntryService from './ItemEntryService';
17
+ import ItemHandler from "./ItemHandler";
18
+ import ItemResourceService from './ItemResourceService';
19
+ import LockService from "./LockService";
20
+ import PromiseService from "./PromiseService";
21
+ import { extend } from './Util';
22
+
23
+ export default class ResourceCache {
24
+ collections: CollectionCache;
25
+ deferredCollections: CollectionCache;
26
+ items: ItemCache;
27
+ collectionOptions = {};
28
+ tabRequestParameters = {};
29
+ callbacks = {onBeforeSave: {}, onSaveSuccess: {}, onSaveWithoutClosing: {}};
30
+
31
+ changesInterval: null | number;
32
+ _hasChanges = false;
33
+ rootItem: ItemEntry;
34
+
35
+ constructor (
36
+ private apiRequestService: ApiRequestServiceInterface,
37
+ private errorHandler: ErrorHandlerInterface,
38
+ private itemHandler: ItemHandler,
39
+ private collectionHandler: CollectionHandler,
40
+ private columnHandler: ColumnHandler,
41
+ private lockService: LockService,
42
+ private itemResourceFactory: ItemResourceFactory,
43
+ private collectionCacheFactory: CollectionCacheFactory,
44
+ private changesProcessorFactory: ChangesProcessorFactory,
45
+ private itemCacheFactory: ItemCacheFactory,
46
+ private collectionResourceFactory: CollectionResourceFactory,
47
+ private itemEntryService: ItemEntryService,
48
+ private itemResourceService: ItemResourceService,
49
+ private promiseService: PromiseService
50
+ ) {
51
+ this.collections = this.collectionCacheFactory.construct();
52
+ this.deferredCollections = this.collectionCacheFactory.construct();
53
+ this.items = this.itemCacheFactory.construct();
54
+ }
55
+
56
+ addSingleItem (itemEntry: ItemEntry, options, isRoot) {
57
+ if (isRoot)
58
+ this.rootItem = itemEntry;
59
+
60
+ if (!this.items.contains(itemEntry.itemType, itemEntry.itemId))
61
+ this.items.add(itemEntry);
62
+
63
+ options = this.itemResourceService.processOptions(options);
64
+ options.onDeactivate = this.clearItem;
65
+
66
+ return this.itemResourceFactory.construct(itemEntry, options);
67
+ }
68
+
69
+ addCollection (collectionEntry: CollectionEntry, resourceOptions) {
70
+ if (!this.collections.contains(collectionEntry)) {
71
+ this.collections.add(collectionEntry);
72
+ }
73
+
74
+ const collectionEntryId = collectionEntry.id;
75
+
76
+ if (this.collectionOptions[collectionEntryId] === undefined)
77
+ this.collectionOptions[collectionEntryId] = {
78
+ edit: resourceOptions.editCollection,
79
+ editItems: resourceOptions.editItems
80
+ };
81
+ else {
82
+ const collectionOptionsEntry = this.collectionOptions[collectionEntryId];
83
+
84
+ collectionOptionsEntry.edit = collectionOptionsEntry.edit || resourceOptions.editCollection;
85
+ collectionOptionsEntry.editItems = collectionOptionsEntry.editItems || resourceOptions.editItems;
86
+ }
87
+
88
+ resourceOptions.onDeactivateItem = this.clearItem;
89
+ resourceOptions.onDeactivateCollection = this.clearCollection;
90
+
91
+ return this.collectionResourceFactory.construct(collectionEntry, resourceOptions);
92
+ }
93
+
94
+ clearCollection (collectionResource: CollectionResource) {
95
+ const collectionEntry: CollectionEntry = collectionResource.entry;
96
+
97
+ this.collections.clear(collectionEntry);
98
+ collectionEntry.clearResources();
99
+ }
100
+
101
+ clearItem (itemResource: ItemResource) {
102
+ const itemEntry: ItemEntry = itemResource.entry;
103
+
104
+ this.items.clear(itemEntry);
105
+ this.itemEntryService.clearResources(itemEntry);
106
+ }
107
+
108
+ clearResources (isSubTabChange = false) {
109
+ this.items.forEach(this.itemEntryService.clearResources);
110
+
111
+ this.collections.forEach(collectionEntry => {
112
+ collectionEntry.clearResources(isSubTabChange);
113
+ }
114
+ );
115
+
116
+ // TODO: remove this condition once crew and transport are no longer loaded in seperate grid instances in the project planner grid
117
+ if (!isSubTabChange)
118
+ this.collections.clear();
119
+ }
120
+
121
+ updateSingleItems (conversionMap) {
122
+ for (const itemType in conversionMap) {
123
+ for (const oldItemId in conversionMap[itemType]) {
124
+ const itemEntry = this.items.get(itemType, oldItemId);
125
+
126
+ if (itemEntry === undefined)
127
+ continue;
128
+
129
+ this.items.updateItemId(itemEntry, oldItemId);
130
+ }
131
+ }
132
+ }
133
+
134
+ // resources will be cleared later (clear function)
135
+ revertChanges () {
136
+ this.items.forEach(this.itemEntryService.resetModified);
137
+ this.collections.forEach(this.resetCollection);
138
+ }
139
+
140
+ resetCollection (collectionEntry: CollectionEntry) {
141
+ const options = this.collectionOptions[collectionEntry.id];
142
+
143
+ if (options.edit)
144
+ collectionEntry.revertChanges();
145
+
146
+ if (!options.editItems)
147
+ return;
148
+
149
+ // these are edit resources (editItems)
150
+ for (const collectionItem of collectionEntry.items)
151
+ this.itemEntryService.resetModified(collectionItem);
152
+ }
153
+
154
+ hasChanges () {
155
+ if (!this._hasChanges)
156
+ this._hasChanges = this.detectChanges();
157
+
158
+ return this._hasChanges;
159
+ }
160
+
161
+ detectChanges () {
162
+ if (this.items.some(this.itemEntryService.hasChanges))
163
+ return true;
164
+
165
+ return this.collections.some((collectionEntry: CollectionEntry) => {
166
+ this.collectionHasChanges(collectionEntry);
167
+ });
168
+ }
169
+
170
+ collectionHasChanges (collectionEntry: CollectionEntry) {
171
+ const options = this.collectionOptions[collectionEntry.id];
172
+
173
+ if (options.edit && (collectionEntry.hasAddedItems() || collectionEntry.hasRemovedItems()))
174
+ return true;
175
+
176
+ return options.editItems && collectionEntry.hasUpdatedItems();
177
+ }
178
+
179
+ isLockable () {
180
+ if (this.rootItem === undefined)
181
+ return false;
182
+
183
+ return this.itemEntryService.isLockable(this.rootItem);
184
+ }
185
+
186
+ save (closeTab = false, parameters, breakUpByParent, onAfterCreateCallback) {
187
+ return (this.isLockable() ? this.lockService.fetchLock(this.rootItem) : this.promiseService.when()).then(
188
+ lockToken => {
189
+ return this.executeCallbacks('onBeforeSave', closeTab);
190
+ },
191
+ this.errorHandler.reject
192
+ ).then(
193
+ () => {
194
+ const requestParameters = extend({}, this.tabRequestParameters, parameters);
195
+
196
+ this.tabRequestParameters = {};
197
+
198
+ return this.changesProcessorFactory.construct().execute(
199
+ this.items.toArray(),
200
+ this.collections.toArray(),
201
+ breakUpByParent,
202
+ requestParameters,
203
+ this.collectionOptions,
204
+ onAfterCreateCallback
205
+ );
206
+ },
207
+ this.errorHandler.reject
208
+ ).then(conversionMap => {
209
+ if (this.isLockable())
210
+ this.lockService.clearLock(this.rootItem);
211
+
212
+ this._hasChanges = false;
213
+
214
+ if (!closeTab)
215
+ this.updateSingleItems(conversionMap);
216
+
217
+ return this.executeCallbacks('onSaveSuccess', conversionMap).then(
218
+ () => {
219
+ if (!closeTab)
220
+ this.executeCallbacks('onSaveWithoutClosing', conversionMap);
221
+
222
+ return conversionMap;
223
+ },
224
+ this.errorHandler.reject
225
+ );
226
+ },
227
+ this.errorHandler.reject
228
+ );
229
+ }
230
+
231
+ executeCallbacks (event, callbackParameter) {
232
+ const result = {};
233
+
234
+ for (const key in this.callbacks[event])
235
+ result[key] = this.callbacks[event][key](callbackParameter);
236
+
237
+ return this.promiseService.all(result);
238
+ }
239
+
240
+ enableChangesWatcher () {
241
+ if (this.changesInterval !== null) {
242
+ console.warn('Changes watcher already running!', this);
243
+ this.disableChangesWatcher();
244
+ }
245
+
246
+ this.changesInterval = setInterval(
247
+ () => {
248
+ if (!this.hasChanges())
249
+ return;
250
+
251
+ this.disableChangesWatcher();
252
+
253
+ if (this.isLockable())
254
+ this.lockService.fetchLock(this.rootItem);
255
+ },
256
+ 3000
257
+ );
258
+ }
259
+
260
+ /**
261
+ * @deprecated
262
+ */
263
+ enableWatcher () {
264
+ this.enableChangesWatcher();
265
+ }
266
+
267
+ disableChangesWatcher () {
268
+ clearInterval(this.changesInterval);
269
+ this.changesInterval = null;
270
+ }
271
+
272
+ /**
273
+ * @deprecated
274
+ */
275
+ disableWatcher () {
276
+ this.disableChangesWatcher();
277
+ }
278
+
279
+ /**
280
+ * @deprecated
281
+ */
282
+ registerCallback (event: string, key: string, callback) {
283
+ this.callbacks[event][key] = callback;
284
+
285
+ return () => {
286
+ delete this.callbacks[event][key];
287
+ };
288
+ }
289
+ }