@processmaker/screen-builder 2.5.27 → 2.5.28

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@processmaker/screen-builder",
3
- "version": "2.5.27",
3
+ "version": "2.5.28",
4
4
  "scripts": {
5
5
  "serve": "vue-cli-service serve",
6
6
  "build": "vue-cli-service build",
@@ -77,6 +77,7 @@
77
77
  "vue-template-compiler": "^2.6.12",
78
78
  "vue-uniq-ids": "^1.0.0",
79
79
  "vue-upload-component": "^2.8.14",
80
+ "vue-vuelidate-jsonschema": "^0.13.4",
80
81
  "vuedraggable": "^2.16.0",
81
82
  "vuetable-2": "^1.7.5",
82
83
  "vuex": "^3.1.1"
@@ -4,6 +4,7 @@ import _ from 'lodash';
4
4
 
5
5
  export default {
6
6
  screensCache: [],
7
+ cachedScreenPromises: [],
7
8
 
8
9
  install(Vue) {
9
10
  Vue.prototype.$dataProvider = this;
@@ -77,23 +78,32 @@ export default {
77
78
  }
78
79
  });
79
80
  },
80
- getScreen(id, query = '') {
81
- const endpoint = _.get(window, 'PM4ConfigOverrides.getScreenEndpoint', '/screens');
82
- return new Promise((resolve, reject) => {
83
- const cache = this.screensCache.find(screen => screen.id == id);
84
- if (cache) {
85
- resolve({data: cache});
86
- }
87
- if (!cache && id != undefined) {
88
- const request = this.get(endpoint + `/${id}${query}`);
89
- request.then(response => {
90
- if (response.data.nested) {
91
- this.addNestedScreenCache(response.data.nested);
92
- }
93
- resolve(response);
94
- }).catch(response => reject(response));
81
+ getScreen(id, query='') {
82
+ let cachedPromise = this.cachedScreenPromises.find(item => item.id === id && item.query === query);
83
+ if (cachedPromise) {
84
+ return cachedPromise.screenPromise;
85
+ }
86
+ else {
87
+ const endpoint = _.get(window, 'PM4ConfigOverrides.getScreenEndpoint', '/screens');
88
+
89
+ const screensCacheHit = this.screensCache.find(screen => screen.id == id);
90
+ if (screensCacheHit) {
91
+ return Promise.resolve({data: screensCacheHit});
95
92
  }
96
- });
93
+
94
+ let screenPromise = new Promise((resolve, reject) => {
95
+ this.get(endpoint + `/${id}${query}`)
96
+ .then(response => {
97
+ if (response.data.nested) {
98
+ this.addNestedScreenCache(response.data.nested);
99
+ }
100
+ resolve(response);
101
+ })
102
+ .catch(response => reject(response));
103
+ });
104
+ this.cachedScreenPromises.push({id, query, screenPromise});
105
+ return screenPromise;
106
+ }
97
107
  },
98
108
 
99
109
  postScript(id, params) {
@@ -57,8 +57,8 @@
57
57
  </div>
58
58
  </b-card-header>
59
59
  <b-collapse :id="formatRuleContentAsId(rule.content)" :accordion="formatRuleContentAsId(rule.content)" :visible="rule.visible" role="tabpanel">
60
- <b-card-body>
61
- <div class="p-2">
60
+ <b-card-body>
61
+ <div class="p-2">
62
62
  <div v-for="config in rule.configs" :key="config.label" class="mb-2">
63
63
  <div v-if="config.type === 'FormInput'">
64
64
  <form-input :label="config.label" :name="config.name || config.label" v-model="config.value" :validation="config.validation" :helper="config.helper"/>
@@ -294,7 +294,7 @@ export default {
294
294
  if (this.rules && this.rules.length) {
295
295
  return true;
296
296
  }
297
-
297
+
298
298
  return false;
299
299
  },
300
300
  },
@@ -314,9 +314,8 @@ export default {
314
314
  },
315
315
  },
316
316
  value() {
317
- this.rules = this.value;
317
+ this.rules = this.value || [];
318
318
  this.cloneSetRules();
319
-
320
319
  },
321
320
  selectedOption: {
322
321
  deep: true,
@@ -380,13 +379,13 @@ export default {
380
379
  }
381
380
  });
382
381
 
383
- if (ruleConfigs.length > 1) {
382
+ if (ruleConfigs.length > 1) {
384
383
  ruleConfigs = ruleConfigs.join(',');
385
384
  }
386
385
  if (ruleConfigs.length) {
387
386
  rule.value = rule.field + ruleConfigs;
388
387
  }
389
-
388
+
390
389
  }
391
390
  });
392
391
  },
@@ -5,6 +5,7 @@
5
5
  </template>
6
6
 
7
7
  <script>
8
+ import Mustache from 'mustache';
8
9
  import { getValidPath } from '@/mixins';
9
10
 
10
11
  export default {
@@ -18,6 +19,45 @@ export default {
18
19
  ['btn-' + variant]: true,
19
20
  };
20
21
  },
22
+ options() {
23
+ if (!this.tooltip || this.event === 'submit') {
24
+ return {};
25
+ }
26
+
27
+ let content = '';
28
+ try {
29
+ content = Mustache.render(this.tooltip.content || '', this.transientData);
30
+ } catch (error) { error; }
31
+
32
+ return {
33
+ title: content,
34
+ html: true,
35
+ placement: this.tooltip.position || '',
36
+ trigger: 'hover',
37
+ variant: this.tooltip.variant || '',
38
+ boundary: 'window',
39
+ };
40
+ },
41
+ valid() {
42
+ if (this.$attrs.validate) {
43
+ return !this.$attrs.validate.$invalid;
44
+ }
45
+ return true;
46
+ },
47
+ message() {
48
+ // eslint-disable-next-line vue/no-side-effects-in-computed-properties
49
+ this.errors = 0;
50
+ if (!this.valid) {
51
+ this.countErrors(this.$attrs.validate.vdata);
52
+ this.countErrors(this.$attrs.validate.schema);
53
+ let message = 'There are {{items}} validation errors in your form.';
54
+ if (this.errors === 1) {
55
+ message = 'There is a validation error in your form.';
56
+ }
57
+ return this.$t(message, {items: this.errors});
58
+ }
59
+ return '';
60
+ },
21
61
  },
22
62
  data() {
23
63
  return {
@@ -11,7 +11,7 @@ import Json2Vue from '../mixins/Json2Vue';
11
11
  import CurrentPageProperty from '../mixins/CurrentPageProperty';
12
12
  import WatchersSynchronous from '@/components/watchers-synchronous';
13
13
  import ScreenRendererError from '../components/renderer/screen-renderer-error';
14
- import { cloneDeep, isEqual } from 'lodash';
14
+ import { cloneDeep, isEqual, debounce } from 'lodash';
15
15
 
16
16
  export default {
17
17
  name: 'screen-renderer',
@@ -27,19 +27,29 @@ export default {
27
27
  mounted() {
28
28
  this.currentDefinition = cloneDeep(this.definition);
29
29
  this.component = this.buildComponent(this.currentDefinition);
30
+ this.rebuildScreen = debounce(this.rebuildScreen, 25);
30
31
  },
31
32
  watch: {
32
33
  definition: {
33
34
  deep: true,
34
35
  handler(definition) {
35
- if (!isEqual(definition, this.currentDefinition)) {
36
- this.currentDefinition = cloneDeep(definition);
37
- this.component = this.buildComponent(this.currentDefinition);
38
- }
36
+ this.rebuildScreen(definition);
39
37
  },
40
38
  },
41
39
  },
42
40
  methods: {
41
+ rebuildScreen(definition) {
42
+ if (!isEqual(definition, this.currentDefinition)) {
43
+ this.currentDefinition = cloneDeep(definition);
44
+ this.component = this.buildComponent(this.currentDefinition);
45
+ }
46
+ },
47
+ onAsyncWatcherOn() {
48
+ this.displayAsyncLoading = typeof this._parent === 'undefined';
49
+ },
50
+ onAsyncWatcherOff() {
51
+ this.displayAsyncLoading = false;
52
+ },
43
53
  getCurrentPage() {
44
54
  return this.$refs.component.getCurrentPage();
45
55
  },
@@ -55,10 +55,10 @@
55
55
  </template>
56
56
 
57
57
  <script>
58
- import _ from 'lodash';
58
+ import { get } from 'lodash';
59
59
 
60
60
  const defaultBeforeLoadTask = () => {
61
- new Promise((resolve) => {
61
+ return new Promise((resolve) => {
62
62
  resolve();
63
63
  });
64
64
  };
@@ -177,8 +177,8 @@ export default {
177
177
  if (this.screen.type === 'CONVERSATIONAL') {
178
178
  this.renderComponent = 'ConversationalForm';
179
179
  } else {
180
- const isInterstitial = _.get(this.screen, '_interstitial', false);
181
- let component = _.get(this, 'task.component', 'task-screen');
180
+ const isInterstitial = get(this.screen, '_interstitial', false);
181
+ let component = get(this, 'task.component', 'task-screen');
182
182
  if (component === null || isInterstitial) {
183
183
  component = 'task-screen';
184
184
  }
@@ -235,24 +235,28 @@ export default {
235
235
  });
236
236
  }
237
237
  },
238
- async loadTask() {
239
- await this.beforeLoadTask(this.taskId, this.nodeId);
238
+ loadTask() {
239
+ const url = `/${this.taskId}?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested`;
240
+ // For Vocabularies
241
+ if (window.ProcessMaker && window.ProcessMaker.packages && window.ProcessMaker.packages.includes('package-vocabularies')) {
242
+ window.ProcessMaker.VocabulariesSchemaUrl = `vocabularies/task_schema/${this.taskId}`;
243
+ }
240
244
 
241
- return this.$dataProvider
242
- .getTasks(
243
- `/${this.taskId}?include=data,user,requestor,processRequest,component,screen,requestData,bpmnTagName,interstitial,definition,nested`
244
- )
245
- .then((response) => {
246
- this.task = response.data;
247
- this.checkTaskStatus();
248
- })
249
- .catch(() => {
250
- this.hasErrors = true;
251
- });
245
+ return this.beforeLoadTask(this.taskId, this.nodeId).then(() => {
246
+ this.$dataProvider
247
+ .getTasks(url)
248
+ .then((response) => {
249
+ this.task = response.data;
250
+ this.checkTaskStatus();
251
+ })
252
+ .catch(() => {
253
+ this.hasErrors = true;
254
+ });
255
+ });
252
256
  },
253
257
  prepareTask() {
254
258
  this.resetScreenState();
255
- this.requestData = _.get(this.task, 'request_data', {});
259
+ this.requestData = get(this.task, 'request_data', {});
256
260
  this.refreshScreen++;
257
261
 
258
262
  this.$emit('task-updated', this.task);
@@ -2,7 +2,7 @@ import extensions from './extensions';
2
2
  import ScreenBase from './ScreenBase';
3
3
  import CountElements from '../CountElements';
4
4
  import ValidationsFactory from '../ValidationsFactory';
5
- import _ from 'lodash';
5
+ import _, { debounce, isEqual } from 'lodash';
6
6
 
7
7
  let screenRenderer;
8
8
 
@@ -322,19 +322,43 @@ export default {
322
322
  },
323
323
  addValidationRulesLoader(component, definition) {
324
324
  const firstPage = parseInt(this.currentPage) || 0;
325
+ function getKeys(input) {
326
+ if (input instanceof Array) {
327
+ const response = [];
328
+ input.forEach((item) => {
329
+ response.push(getKeys(item));
330
+ });
331
+ return response;
332
+ }
333
+ if (!(input instanceof Object)) {
334
+ return typeof input;
335
+ }
336
+ const keys = Object.keys(input);
337
+ const response = {};
338
+ keys.forEach((key) => {
339
+ response[key] = getKeys(input[key]);
340
+ });
341
+ return response;
342
+ }
343
+ let updateValidationRules = function(screenComponent, validations) {
344
+ const a = getKeys(screenComponent.ValidationRules__);
345
+ const b = getKeys(validations);
346
+ if (isEqual(a, b)) {
347
+ return;
348
+ }
349
+ screenComponent.ValidationRules__ = validations;
350
+ screenComponent.$nextTick(() => {
351
+ if (screenComponent.$v) {
352
+ screenComponent.$v.$touch();
353
+ }
354
+ });
355
+ };
356
+ updateValidationRules = debounce(updateValidationRules, 25);
325
357
  component.methods.loadValidationRules = function() {
326
358
  // Asynchronous loading of validations
327
359
  const validations = {};
328
360
  ValidationsFactory(definition, { screen: definition, firstPage, data: {_parent: this._parent, ...this.vdata} }).addValidations(validations).then(() => {
329
- if (_.isEqual(this.ValidationRules__, validations)) {
330
- return;
331
- }
332
- this.ValidationRules__ = validations;
333
- this.$nextTick(() => {
334
- if (this.$v) {
335
- this.$v.$touch();
336
- }
337
- });
361
+ updateValidationRules(this, validations);
338
362
  });
339
363
  };
340
364
  component.mounted.push('this.loadValidationRules()');
@@ -5,6 +5,22 @@ import { ValidationMsg } from './ValidationRules';
5
5
  const stringFormats = ['string', 'datetime', 'date', 'password'];
6
6
 
7
7
  export default {
8
+ schema: [
9
+ function() {
10
+ if (window.ProcessMaker && window.ProcessMaker.packages && window.ProcessMaker.packages.includes('package-vocabularies')) {
11
+ if (window.ProcessMaker.VocabulariesSchemaUrl) {
12
+ let response = window.ProcessMaker.apiClient.get(window.ProcessMaker.VocabulariesSchemaUrl);
13
+ return response.then(response => {
14
+ return response.data;
15
+ });
16
+ }
17
+ if (window.ProcessMaker.VocabulariesPreview) {
18
+ return window.ProcessMaker.VocabulariesPreview;
19
+ }
20
+ }
21
+ return {};
22
+ },
23
+ ],
8
24
  data() {
9
25
  return {
10
26
  ValidationRules__: {},
@@ -178,6 +194,11 @@ export default {
178
194
  if (validation[key]!==undefined && !validation[key]) {
179
195
  message.push(this.$t(ValidationMsg[key]).replace(/\{(.+?)\}/g,(match,p1)=>{return validation.$params[key][p1];}));
180
196
  }
197
+ // JSON Schema use to start with 'schema'
198
+ const keyForSchema = 'schema' + key.charAt(0).toUpperCase() + key.slice(1);
199
+ if (validation[keyForSchema]!==undefined && !validation[keyForSchema]) {
200
+ message.push(this.$t(ValidationMsg[key]).replace(/\{(.+?)\}/g,(match,p1)=>{return validation.$params[keyForSchema][p1];}));
201
+ }
181
202
  });
182
203
  return message.join('.\n');
183
204
  },
@@ -161,17 +161,29 @@ export const required = (value) => {
161
161
  export const requiredIf = (variable, expected, fieldName) => helpers.withParams({variable, expected}, function(value, data) {
162
162
  const level = fieldName.split('.').length - 1;
163
163
  const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
164
- if (get(dataWithParent, variable) != expected) return true;
164
+ const variableValue = get(dataWithParent, variable);
165
+ const isBoolean = (variableValue === true || variableValue === false);
166
+ let expectedValue = expected;
167
+ if (isBoolean) {
168
+ expectedValue = expected === 'true' || expected === '1';
169
+ }
170
+ if (variableValue != expectedValue) return true;
165
171
  return value instanceof Array ? value.length > 0 : !!value;
166
172
  });
167
173
 
168
174
  export const requiredUnless = (variable, expected, fieldName) => helpers.withParams({variable, expected}, function(value, data) {
169
175
  const level = fieldName.split('.').length - 1;
170
176
  const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
171
- if (get(dataWithParent, variable) == expected) return true;
177
+ const variableValue = get(dataWithParent, variable);
178
+ const isBoolean = (variableValue === true || variableValue === false);
179
+ let expectedValue = expected;
180
+ if (isBoolean) {
181
+ expectedValue = expected === 'true' || expected === '1';
182
+ }
183
+ if (variableValue == expectedValue) return true;
172
184
  return value instanceof Array ? value.length > 0 : !!value;
173
185
  });
174
-
186
+
175
187
  export const sameAs = (field, fieldName) => helpers.withParams({field}, function(value, data) {
176
188
  const level = fieldName.split('.').length - 1;
177
189
  const dataWithParent = this.getDataAccordingToFieldLevel(this.getRootScreen().addReferenceToParents(data), level);
@@ -12,8 +12,8 @@ export default {
12
12
  this.getValue(${JSON.stringify(v.name)}, data) ||
13
13
  this.initialValue('${component}', '${dataFormat}', ${JSON.stringify(v.config)})
14
14
  `);
15
- this.addWatch(screen, v.name, `this.setValue(${JSON.stringify(v.name)}, value, this.vdata);`);
16
- this.addWatch(screen, `vdata.${v.name}`, `this.${v.name} = this.vdata.${v.name};`);
15
+ this.addWatch(screen, v.name, `this.setValue(${JSON.stringify(v.name)}, value, this.vdata);this.setValue(${JSON.stringify(v.name)}, value, this.schema);`);
16
+ this.addWatch(screen, `vdata.${v.name}`, `this.setValue(${JSON.stringify(v.name)}, value, this);`);
17
17
  });
18
18
  screen.props.vdata = null;
19
19
  },
@@ -0,0 +1,11 @@
1
+ export default {
2
+ mounted() {
3
+ this.extensions.push({
4
+ onloadproperties({ element, properties }) {
5
+ if (element.component === 'FormButton' && element.config.event === 'submit') {
6
+ properties[':validate'] = '$v';
7
+ }
8
+ },
9
+ });
10
+ },
11
+ };
@@ -1,4 +1,5 @@
1
1
  import { validationMixin } from 'vuelidate';
2
+ import VueVuelidateJsonschema from 'vue-vuelidate-jsonschema';
2
3
 
3
4
  export default {
4
5
  mounted() {
@@ -9,12 +10,13 @@ export default {
9
10
  delete properties[':validation'];
10
11
  delete properties['validation'];
11
12
  // Add validation class and error message
12
- properties[':class'] = `{ 'form-group--error': ${this.checkVariableExists('$v.vdata.' + element.config.name)} && $v.vdata.${element.config.name}.$invalid }`;
13
- properties[':error'] = `${this.checkVariableExists('$v.vdata.' + element.config.name)} && validationMessage($v.vdata.${element.config.name})`;
13
+ properties[':class'] = `{ 'form-group--error': ${this.checkVariableExists('$v.vdata.' + element.config.name)} && $v.vdata.${element.config.name}.$invalid || ${this.checkVariableExists('$v.schema.' + element.config.name)} && $v.schema.${element.config.name}.$invalid }`;
14
+ properties[':error'] = `${this.checkVariableExists('$v.vdata.' + element.config.name)} && validationMessage($v.vdata.${element.config.name}) || ${this.checkVariableExists('$v.schema.' + element.config.name)} && validationMessage($v.schema.${element.config.name})`;
14
15
  }
15
16
  },
16
17
  onbuild({ screen }) {
17
18
  screen.mixins.push(validationMixin);
19
+ screen.mixins.push(VueVuelidateJsonschema.mixin);
18
20
  },
19
21
  });
20
22
  },