@eclipse-scout/core 11.0.32 → 11.0.41

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/dist/file-list CHANGED
@@ -1,5 +1,5 @@
1
- eclipse-scout-core-466592e50380384291c7.min.js
2
- eclipse-scout-core-466592e50380384291c7.min.js.map
1
+ eclipse-scout-core-f05c37a693e6abaa57dc.min.js
2
+ eclipse-scout-core-f05c37a693e6abaa57dc.min.js.map
3
3
  eclipse-scout-core-theme-d1dee22d4f9b9233a432.min.css
4
4
  eclipse-scout-core-theme-dark-7ead007f849a9c1a6ec1.min.css
5
5
  eclipse-scout-core-theme-dark.css
package/dist/texts.json CHANGED
@@ -127,6 +127,8 @@
127
127
  "ui.PleaseWait_": "Please wait",
128
128
  "ui.PopupBlockerDetected": "Opening a new window automatically was blocked by the browser.",
129
129
  "ui.Reconnecting_": "Reconnecting...",
130
+ "ui.RejectedUpload": "Rejected File Upload",
131
+ "ui.RejectedUploadMsg": "The uploaded file was rejected by the security policy.",
130
132
  "ui.Reload": "Reload",
131
133
  "ui.ReloadData": "Reload data",
132
134
  "ui.RemoveFilter": "Remove filter",
@@ -298,6 +300,8 @@
298
300
  "ui.PleaseWait_": "Veuillez attendre",
299
301
  "ui.PopupBlockerDetected": "L'ouverture d'une nouvelle fenêtre a été empêchée par la navigateur.",
300
302
  "ui.Reconnecting_": "Connexion …",
303
+ "ui.RejectedUpload": "Téléchargement de fichier rejeté",
304
+ "ui.RejectedUploadMsg": "Le fichier téléchargé a été rejeté par la politique de sécurité.",
301
305
  "ui.Reload": "Charger à nouveau",
302
306
  "ui.ReloadData": "Nouveau chargement des données",
303
307
  "ui.RemoveFilter": "Supprimer le filtre",
@@ -469,6 +473,8 @@
469
473
  "ui.PleaseWait_": "Attendere prego",
470
474
  "ui.PopupBlockerDetected": "Apertura automatica di una nuova finestra impedita dal browser.",
471
475
  "ui.Reconnecting_": "Collegamento...",
476
+ "ui.RejectedUpload": "Caricamento file rifiutato",
477
+ "ui.RejectedUploadMsg": "Il file caricato è stato rifiutato dalla politica di sicurezza.",
472
478
  "ui.Reload": "Ricarica",
473
479
  "ui.ReloadData": "Ricarica dati",
474
480
  "ui.RemoveFilter": "Rimuovere filtro",
@@ -645,6 +651,8 @@
645
651
  "ui.PleaseWait_": "Bitte warten",
646
652
  "ui.PopupBlockerDetected": "Das automatische Öffnen eines neuen Fensters wurde durch den Browser verhindert.",
647
653
  "ui.Reconnecting_": "Verbinde...",
654
+ "ui.RejectedUpload": "Abgelehnte Datei",
655
+ "ui.RejectedUploadMsg": "Die hochgeladene Datei wurde durch die Sicherheitsrichtlinie abgelehnt.",
648
656
  "ui.Reload": "Neu laden",
649
657
  "ui.ReloadData": "Daten neu laden",
650
658
  "ui.RemoveFilter": "Filter entfernen",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@eclipse-scout/core",
3
- "version": "11.0.32",
3
+ "version": "11.0.41",
4
4
  "description": "Eclipse Scout runtime",
5
5
  "author": "BSI Business Systems Integration AG",
6
6
  "homepage": "https://www.eclipse.org/scout",
@@ -40,7 +40,7 @@
40
40
  "release-postdependency": "releng-scripts release-publish-dependency"
41
41
  },
42
42
  "devDependencies": {
43
- "@eclipse-scout/cli": "11.0.32",
43
+ "@eclipse-scout/cli": "11.0.41",
44
44
  "@eclipse-scout/releng": "^10.0.0",
45
45
  "jasmine-core": "3.6.0",
46
46
  "jasmine-ajax": "4.0.0",
@@ -36,6 +36,10 @@ export default class PlainTextEncoder {
36
36
  // Separate td with ' '
37
37
  text = text.replace(/<\/td>/gi, ' ');
38
38
 
39
+ if (options.removeFontIcons) {
40
+ text = text.replace(/<span\s+class="[^"]*font-icon[^"]*">[^<]*<\/span>/gmi, '');
41
+ }
42
+
39
43
  // Replace remaining tags
40
44
  text = text.replace(/<[^>]+>/gi, '');
41
45
 
@@ -80,9 +80,6 @@ export default class ProposalField extends SmartField {
80
80
  if (this.trimText) {
81
81
  validValue = validValue.trim();
82
82
  }
83
- if (validValue.length > this.maxLength) {
84
- validValue = validValue.substring(0, this.maxLength);
85
- }
86
83
  if (validValue === '') {
87
84
  validValue = null;
88
85
  }
@@ -207,10 +204,6 @@ export default class ProposalField extends SmartField {
207
204
  this.setProperty('trimText', trimText);
208
205
  }
209
206
 
210
- setMaxLength(maxLength) {
211
- this.setProperty('maxLength', maxLength);
212
- }
213
-
214
207
  /**
215
208
  * @override ValueField.js
216
209
  */
@@ -63,6 +63,8 @@ export default class SmartField extends ValueField {
63
63
  // only when the result is up-to-date, we can use the selected lookup row
64
64
  this.initActiveFilter = null;
65
65
  this.disabledCopyOverlay = true;
66
+ this.maxLength = 500;
67
+ this.maxLengthHandler = scout.create('MaxLengthHandler', {target: this});
66
68
 
67
69
  this._addCloneProperties(['lookupRow', 'codeType', 'lookupCall', 'activeFilter', 'activeFilterEnabled', 'activeFilterLabels',
68
70
  'browseHierarchy', 'browseMaxRowCount', 'browseAutoExpandAll', 'browseLoadIncremental', 'searchRequired', 'columnDescriptors',
@@ -150,6 +152,7 @@ export default class SmartField extends ValueField {
150
152
  .on('input', this._onFieldInput.bind(this));
151
153
  }
152
154
  this.addField($field);
155
+ this.maxLengthHandler.install($field);
153
156
 
154
157
  if (!this.embedded) {
155
158
  this.addMandatoryIndicator();
@@ -159,6 +162,11 @@ export default class SmartField extends ValueField {
159
162
  this.addStatus();
160
163
  }
161
164
 
165
+ _renderProperties() {
166
+ super._renderProperties();
167
+ this._renderMaxLength();
168
+ }
169
+
162
170
  _renderGridData() {
163
171
  super._renderGridData();
164
172
  this.updateInnerAlignment({
@@ -588,6 +596,14 @@ export default class SmartField extends ValueField {
588
596
  this.$field.setTabbable(this.enabledComputed);
589
597
  }
590
598
 
599
+ setMaxLength(maxLength) {
600
+ this.setProperty('maxLength', maxLength);
601
+ }
602
+
603
+ _renderMaxLength() {
604
+ this.maxLengthHandler.render();
605
+ }
606
+
591
607
  setLookupCall(lookupCall) {
592
608
  this.setProperty('lookupCall', lookupCall);
593
609
  }
@@ -8,20 +8,7 @@
8
8
  * Contributors:
9
9
  * BSI Business Systems Integration AG - initial API and implementation
10
10
  */
11
- import {
12
- arrays,
13
- BasicField,
14
- fields,
15
- InputFieldKeyStrokeContext,
16
- objects,
17
- scout,
18
- Status,
19
- StringFieldCtrlEnterKeyStroke,
20
- StringFieldEnterKeyStroke,
21
- StringFieldLayout,
22
- strings,
23
- texts
24
- } from '../../../index';
11
+ import {BasicField, fields, InputFieldKeyStrokeContext, objects, scout, Status, StringFieldCtrlEnterKeyStroke, StringFieldEnterKeyStroke, StringFieldLayout, strings, texts} from '../../../index';
25
12
 
26
13
  export default class StringField extends BasicField {
27
14
  constructor() {
@@ -32,6 +19,7 @@ export default class StringField extends BasicField {
32
19
  this.inputMasked = false;
33
20
  this.inputObfuscated = false;
34
21
  this.maxLength = 4000;
22
+ this.maxLengthHandler = scout.create('MaxLengthHandler', {target: this});
35
23
  this.multilineText = false;
36
24
  this.selectionStart = 0;
37
25
  this.selectionEnd = 0;
@@ -90,9 +78,9 @@ export default class StringField extends BasicField {
90
78
  } else {
91
79
  $field = fields.makeTextField(this.$parent);
92
80
  }
93
- $field.on('paste', this._onFieldPaste.bind(this));
94
81
 
95
82
  this.addField($field);
83
+ this.maxLengthHandler.install($field);
96
84
  this.addStatus();
97
85
  }
98
86
 
@@ -185,35 +173,6 @@ export default class StringField extends BasicField {
185
173
  return super.isClearable() && !this.multilineText;
186
174
  }
187
175
 
188
- setMaxLength(maxLength) {
189
- this.setProperty('maxLength', maxLength);
190
- }
191
-
192
- _renderMaxLength() {
193
- // Check if "maxLength" attribute is supported by browser
194
- if (this.$field[0].maxLength) {
195
- this.$field.attr('maxlength', this.maxLength);
196
- } else {
197
- // Fallback for IE9
198
- this.$field.on('keyup paste', e => {
199
- setTimeout(truncate.bind(this), 0);
200
- });
201
- }
202
-
203
- // Make sure current text does not exceed max length
204
- truncate.call(this);
205
- if (!this.rendering) {
206
- this.parseAndSetValue(this._readDisplayText());
207
- }
208
-
209
- function truncate() {
210
- let text = this.$field.val();
211
- if (text.length > this.maxLength) {
212
- this.$field.val(text.slice(0, this.maxLength));
213
- }
214
- }
215
- }
216
-
217
176
  setSelectionStart(selectionStart) {
218
177
  this.setProperty('selectionStart', selectionStart);
219
178
  }
@@ -432,6 +391,14 @@ export default class StringField extends BasicField {
432
391
  });
433
392
  }
434
393
 
394
+ setMaxLength(maxLength) {
395
+ this.setProperty('maxLength', maxLength);
396
+ }
397
+
398
+ _renderMaxLength() {
399
+ this.maxLengthHandler.render();
400
+ }
401
+
435
402
  _onIconClick(event) {
436
403
  this.acceptInput();
437
404
  this.$field.focus();
@@ -561,51 +528,6 @@ export default class StringField extends BasicField {
561
528
  }
562
529
  }
563
530
 
564
- /**
565
- * Get clipboard data, different strategies for browsers.
566
- * Must use a callback because this is required by Chrome's clipboard API.
567
- */
568
- _getClipboardData(event, doneHandler) {
569
- let data = event.originalEvent.clipboardData || this.$container.window(true).clipboardData;
570
- if (data) {
571
- // Chrome, Firefox
572
- if (data.items && data.items.length) {
573
- let item = arrays.find(data.items, item => {
574
- return item.type === 'text/plain';
575
- });
576
- if (item) {
577
- item.getAsString(doneHandler);
578
- }
579
- return;
580
- }
581
-
582
- // IE, Safari
583
- if (data.getData) {
584
- doneHandler(data.getData('Text'));
585
- }
586
- }
587
-
588
- // Can't access clipboard -> don't call done handler
589
- }
590
-
591
- _onFieldPaste(event) {
592
- // must store text and selection because when the callback is executed, the clipboard content has already been applied to the input field
593
- let text = this.$field.val();
594
- let selection = this._getSelection();
595
-
596
- this._getClipboardData(event, pastedText => {
597
- if (!pastedText) {
598
- return;
599
- }
600
-
601
- // Make sure the user is notified about pasted text which is cut off because of maxlength constraints
602
- text = this._applyTextToSelection(text, pastedText, selection);
603
- if (text.length > this.maxLength) {
604
- this._showNotification('ui.PastedTextTooLong');
605
- }
606
- });
607
- }
608
-
609
531
  _showNotification(textKey) {
610
532
  scout.create('DesktopNotification', {
611
533
  parent: this,
@@ -10,9 +10,9 @@
10
10
  */
11
11
  import {
12
12
  arrays,
13
+ fields,
13
14
  HtmlComponent,
14
15
  InputFieldKeyStrokeContext,
15
- fields,
16
16
  keys,
17
17
  LookupCall,
18
18
  scout,
@@ -38,6 +38,8 @@ export default class TagField extends ValueField {
38
38
  this.lookupCall = null;
39
39
  this._currentLookupCall = null;
40
40
  this.tagBar = null;
41
+ this.maxLength = 500;
42
+ this.maxLengthHandler = scout.create('MaxLengthHandler', {target: this});
41
43
  }
42
44
 
43
45
  _init(model) {
@@ -85,12 +87,14 @@ export default class TagField extends ValueField {
85
87
  .on('input', this._onFieldInput.bind(this));
86
88
  this.addFieldContainer($fieldContainer);
87
89
  this.addField($field);
90
+ this.maxLengthHandler.install($field);
88
91
  this.addStatus();
89
92
  }
90
93
 
91
94
  _renderProperties() {
92
95
  super._renderProperties();
93
96
  this._renderValue();
97
+ this._renderMaxLength();
94
98
  }
95
99
 
96
100
  _renderValue() {
@@ -155,6 +159,14 @@ export default class TagField extends ValueField {
155
159
  }
156
160
  }
157
161
 
162
+ setMaxLength(maxLength) {
163
+ this.setProperty('maxLength', maxLength);
164
+ }
165
+
166
+ _renderMaxLength() {
167
+ this.maxLengthHandler.render();
168
+ }
169
+
158
170
  _updateInputVisible() {
159
171
  let visible, oldVisible = !this.$field.isVisible();
160
172
  if (this.enabledComputed) {
package/src/index.js CHANGED
@@ -42,6 +42,7 @@ export {default as fonts} from './util/fonts';
42
42
  export {default as icons} from './util/icons';
43
43
  export {default as inspector} from './util/inspector';
44
44
  export {default as locales} from './util/locales';
45
+ export {default as MaxLengthHandler} from './util/MaxLengthHandler';
45
46
  export {default as logging} from './logging/logging';
46
47
  export {default as NullLogger} from './logging/NullLogger';
47
48
  export {default as mimeTypes} from './util/mimeTypes';
@@ -69,6 +70,7 @@ export {default as HAlign} from './util/HAlign';
69
70
  export {default as aggregation} from './util/aggregation';
70
71
  export {default as webstorage} from './util/webstorage';
71
72
  export {default as cookies} from './util/cookies';
73
+ export {default as ViewportScroller} from './util/ViewportScroller';
72
74
  export {default as ajax} from './ajax/ajax';
73
75
  export {default as AjaxCall} from './ajax/AjaxCall';
74
76
  export {default as AjaxError} from './ajax/AjaxError';
@@ -8,9 +8,38 @@
8
8
  * Contributors:
9
9
  * BSI Business Systems Integration AG - initial API and implementation
10
10
  */
11
- import {arrays, LookupCall, scout} from '../index';
11
+ import {arrays, LookupCall, objects, scout} from '../index';
12
12
  import $ from 'jquery';
13
13
 
14
+ /**
15
+ * A lookup call that can load lookup rows from a REST service.
16
+ *
17
+ * API:
18
+ * ----
19
+ * By default, the REST service is expected to listen for POST requests at the URL defined by
20
+ * this.resourceUrl. It receives a restriction object and must return a list of matching lookup rows.
21
+ * The serialization format is JSON.
22
+ *
23
+ * Lookup rows:
24
+ * ------------
25
+ * The standard lookup row properties defined by Scout are usually sufficient (see AbstractLookupRowDo.java).
26
+ *
27
+ * Restriction:
28
+ * ------------
29
+ * The restriction object consists of a number of 'well-known' properties (e.g. 'text' in QueryBy.TEXT
30
+ * mode, see AbstractLookupRestrictionDo.java for details) and additional, service-dependent properties
31
+ * that can either be predefined in the model or added programmatically at runtime. Since all of those
32
+ * properties are sent in the same restriction object, some care must be taken to prevent accidental
33
+ * overwriting of properties.
34
+ *
35
+ * Order of precedence (lowest to highest):
36
+ * 1. Restrictions automatically applied to all clones after their creation in the respective cloneFor method.
37
+ * These are: 'active' (ALL, TEXT, REC) and 'maxRowCount' (ALL, TEXT, REC)
38
+ * 2. Restrictions predefined in the model property 'restriction', shared by all clones.
39
+ * 3. Restrictions applied to clones programmatically, e.g. during a 'prepareLookupCall' event.
40
+ * 4. Hard-coded properties that are fundamental to the respective queryBy mode (cannot be overridden).
41
+ * These are: 'ids' (KEY, KEYS) and 'text' (TEXT)
42
+ */
14
43
  export default class RestLookupCall extends LookupCall {
15
44
 
16
45
  constructor() {
@@ -42,6 +71,17 @@ export default class RestLookupCall extends LookupCall {
42
71
  this._restriction[key] = value;
43
72
  }
44
73
 
74
+ /**
75
+ * Adds the given key-value pair to 'this._restriction', but only if there is no predefined
76
+ * value for this key in 'this.restriction'. This prevents unintentional overriding of
77
+ * user-defined model restrictions.
78
+ */
79
+ _addRestrictionIfAbsent(key, value) {
80
+ if (!this.restriction || objects.isNullOrUndefined(this.restriction[key])) {
81
+ this.addRestriction(key, value);
82
+ }
83
+ }
84
+
45
85
  _getAll() {
46
86
  return this._call();
47
87
  }
@@ -63,22 +103,22 @@ export default class RestLookupCall extends LookupCall {
63
103
 
64
104
  cloneForAll() {
65
105
  let clone = super.cloneForAll();
66
- clone.addRestriction('active', true);
67
- clone.addRestriction('maxRowCount', this.maxRowCount);
106
+ clone._addRestrictionIfAbsent('active', true);
107
+ clone._addRestrictionIfAbsent('maxRowCount', this.maxRowCount);
68
108
  return clone;
69
109
  }
70
110
 
71
111
  cloneForText(text) {
72
112
  let clone = super.cloneForText(text);
73
- clone.addRestriction('active', true);
74
- clone.addRestriction('maxRowCount', this.maxRowCount);
113
+ clone._addRestrictionIfAbsent('active', true);
114
+ clone._addRestrictionIfAbsent('maxRowCount', this.maxRowCount);
75
115
  return clone;
76
116
  }
77
117
 
78
118
  cloneForRec(parentKey) {
79
119
  let clone = super.cloneForRec(parentKey);
80
- clone.addRestriction('active', true);
81
- clone.addRestriction('maxRowCount', this.maxRowCount);
120
+ clone._addRestrictionIfAbsent('active', true);
121
+ clone._addRestrictionIfAbsent('maxRowCount', this.maxRowCount);
82
122
  return clone;
83
123
  }
84
124
 
@@ -167,7 +207,7 @@ export default class RestLookupCall extends LookupCall {
167
207
  return value;
168
208
  };
169
209
 
170
- let newRestrictions = {};
210
+ let resolvedRestriction = {};
171
211
  let restriction = $.extend({}, this.restriction, this._restriction);
172
212
  Object.keys(restriction).forEach(key => {
173
213
  let value = restriction[key];
@@ -180,11 +220,11 @@ export default class RestLookupCall extends LookupCall {
180
220
  newValue = resolveValue(value);
181
221
  }
182
222
  // Only add non-null restrictions
183
- if (newValue) {
184
- newRestrictions[key] = newValue;
223
+ if (!objects.isNullOrUndefined(newValue)) {
224
+ resolvedRestriction[key] = newValue;
185
225
  }
186
226
  });
187
- return newRestrictions;
227
+ return resolvedRestriction;
188
228
  }
189
229
 
190
230
  _createAjaxCall() {
@@ -104,14 +104,49 @@ export default class Popup extends Widget {
104
104
 
105
105
  // Note that these strings are also used as CSS classes
106
106
  static Alignment = {
107
+ /**
108
+ * The entire popup is positioned horizontally left of the anchor.
109
+ */
107
110
  LEFT: 'left',
111
+ /**
112
+ * With arrow: The arrow at the left edge of the popup is aligned horizontally with the center of the anchor.
113
+ * <p>
114
+ * Without arrow: The left edges of both the popup and the anchor are aligned horizontally.
115
+ */
108
116
  LEFTEDGE: 'leftedge',
117
+ /**
118
+ * The entire popup is positioned vertically above the anchor.
119
+ */
109
120
  TOP: 'top',
121
+ /**
122
+ * With arrow: The arrow at the top edge of the popup is aligned vertically with the center of the anchor.
123
+ * <p>
124
+ * Without arrow: The top edges of both the popup and the anchor are aligned vertically.
125
+ */
110
126
  TOPEDGE: 'topedge',
127
+ /**
128
+ * The centers of both the popup and the anchor are aligned in the respective dimension.
129
+ */
111
130
  CENTER: 'center',
131
+ /**
132
+ * The entire popup is positioned horizontally to the right of the anchor.
133
+ */
112
134
  RIGHT: 'right',
135
+ /**
136
+ * With arrow: The arrow at the right edge of the popup is aligned horizontally with the center of the anchor.
137
+ * <p>
138
+ * Without arrow: The right edges of both the popup and the anchor are aligned horizontally.
139
+ */
113
140
  RIGHTEDGE: 'rightedge',
141
+ /**
142
+ * The entire popup is positioned vertically below the anchor.
143
+ */
114
144
  BOTTOM: 'bottom',
145
+ /**
146
+ * With arrow: The arrow at the bottom edge of the popup is aligned vertically with the center of the anchor.
147
+ * <p>
148
+ * Without arrow: The bottom edges of both the popup and the anchor are aligned vertically.
149
+ */
115
150
  BOTTOMEDGE: 'bottomedge'
116
151
  };
117
152
 
@@ -122,6 +122,7 @@ export default class Session {
122
122
  SESSION_TIMEOUT: 10,
123
123
  UI_PROCESSING: 20,
124
124
  UNSAFE_UPLOAD: 30,
125
+ REJECTED_UPLOAD: 31,
125
126
  VERSION_MISMATCH: 40
126
127
  };
127
128
 
@@ -1048,6 +1049,12 @@ export default class Session {
1048
1049
  boxOptions.yesButtonText = this.optText('ui.Ok', 'Ok');
1049
1050
  boxOptions.yesButtonAction = () => {
1050
1051
  };
1052
+ } else if (jsonError.code === Session.JsonResponseError.REJECTED_UPLOAD) {
1053
+ boxOptions.header = this.optText('ui.RejectedUpload', boxOptions.header);
1054
+ boxOptions.body = this.optText('ui.RejectedUploadMsg', boxOptions.body);
1055
+ boxOptions.yesButtonText = this.optText('ui.Ok', 'Ok');
1056
+ boxOptions.yesButtonAction = () => {
1057
+ };
1051
1058
  }
1052
1059
  this.showFatalMessage(boxOptions, jsonError.code);
1053
1060
  }
@@ -864,13 +864,16 @@ export default class Table extends Widget {
864
864
 
865
865
  if (tooltipText) {
866
866
  return tooltipText;
867
- } else if ($row.data('aggregateRow') && $cell.text().trim() && ($cell.isContentTruncated() || ($cell.children('.table-cell-icon').length && !$cell.children('.table-cell-icon').isVisible()))) {
867
+ }
868
+ if ($row.data('aggregateRow') && $cell.text().trim() && ($cell.isContentTruncated() || ($cell.children('.table-cell-icon').length && !$cell.children('.table-cell-icon').isVisible()))) {
868
869
  $cell = $cell.clone();
869
870
  $cell.children('.table-cell-icon').setVisible(true);
870
871
  return $cell.html();
871
- } else if (this._isTruncatedCellTooltipEnabled(column) && $cell.isContentTruncated()) {
872
+ }
873
+ if (this._isTruncatedCellTooltipEnabled(column) && $cell.isContentTruncated()) {
872
874
  return strings.plainText($cell.html(), {
873
- trim: true
875
+ trim: true,
876
+ removeFontIcons: true
874
877
  });
875
878
  }
876
879
  }
@@ -294,7 +294,8 @@ export default class TableHeader extends Widget {
294
294
  $col = $col.clone();
295
295
  $col.children('.table-header-item-state').remove();
296
296
  let text = strings.plainText($col.html(), {
297
- trim: true
297
+ trim: true,
298
+ removeFontIcons: true
298
299
  });
299
300
  if (strings.hasText(text)) {
300
301
  return text;
@@ -0,0 +1,119 @@
1
+ /*
2
+ * Copyright (c) 2010-2021 BSI Business Systems Integration AG.
3
+ * All rights reserved. This program and the accompanying materials
4
+ * are made available under the terms of the Eclipse Public License v1.0
5
+ * which accompanies this distribution, and is available at
6
+ * http://www.eclipse.org/legal/epl-v10.html
7
+ *
8
+ * Contributors:
9
+ * BSI Business Systems Integration AG - initial API and implementation
10
+ */
11
+ import {arrays, objects, scout, Status} from '../index';
12
+ import $ from 'jquery';
13
+ import {assertValue} from '../scout';
14
+
15
+ export default class MaxLengthHandler {
16
+
17
+ constructor(options) {
18
+ options = options || {};
19
+ assertValue(options.target, 'target is mandatory');
20
+
21
+ this.$textInputField = null;
22
+ this.onInputFieldPaste = this._onInputFieldPaste.bind(this);
23
+ $.extend(this, options);
24
+ }
25
+
26
+ install($textInputField) {
27
+ this.uninstall();
28
+ if (!$textInputField || (!$textInputField.is('input:text') && !$textInputField.is('textarea'))) {
29
+ return;
30
+ }
31
+ if ($textInputField) {
32
+ this.$textInputField = $textInputField;
33
+ this.$textInputField.on('paste', this.onInputFieldPaste);
34
+ }
35
+ }
36
+
37
+ uninstall() {
38
+ if (this.$textInputField) {
39
+ this.$textInputField.off('paste', this.onInputFieldPaste);
40
+ }
41
+ }
42
+
43
+ render() {
44
+ if (!this.$textInputField || objects.isNullOrUndefined(this.target.maxLength)) {
45
+ return;
46
+ }
47
+ this.$textInputField.attr('maxlength', this.target.maxLength);
48
+
49
+ // Make sure current text does not exceed max length
50
+ let text = this.$textInputField.val();
51
+ if (text.length > this.target.maxLength) {
52
+ this.$textInputField.val(text.slice(0, this.target.maxLength));
53
+ }
54
+ if (!this.target.rendering) {
55
+ this.target.parseAndSetValue(this.target._readDisplayText());
56
+ }
57
+ }
58
+
59
+ _onInputFieldPaste(event) {
60
+ if (!this.$textInputField || objects.isNullOrUndefined(this.target.maxLength)) {
61
+ return;
62
+ }
63
+ // must read out the text and selection size now because when the callback is executed, the clipboard content has already been applied to the input field
64
+ let textSize = this.$textInputField.val().length - this._getSelectionSize();
65
+
66
+ this._getClipboardData(event, pastedText => {
67
+ if (!pastedText) {
68
+ return;
69
+ }
70
+ if ((textSize + pastedText.length) > this.target.maxLength) {
71
+ this._showNotification('ui.PastedTextTooLong');
72
+ }
73
+ });
74
+ }
75
+
76
+ _getSelectionSize() {
77
+ let start = scout.nvl(this.$textInputField[0].selectionStart, null);
78
+ let end = scout.nvl(this.$textInputField[0].selectionEnd, null);
79
+ if (start === null || end === null) {
80
+ return 0;
81
+ }
82
+ return end - start;
83
+ }
84
+
85
+ /**
86
+ * Get clipboard data, different strategies for browsers.
87
+ * Must use a callback because this is required by Chrome's clipboard API.
88
+ */
89
+ _getClipboardData(event, doneHandler) {
90
+ let data = event.originalEvent.clipboardData || this.target.$container.window(true).clipboardData;
91
+ if (data) {
92
+ // Chrome, Firefox
93
+ if (data.items && data.items.length) {
94
+ let item = arrays.find(data.items, item => {
95
+ return item.type === 'text/plain';
96
+ });
97
+ if (item) {
98
+ item.getAsString(doneHandler);
99
+ }
100
+ return;
101
+ }
102
+
103
+ // IE, Safari
104
+ if (data.getData) {
105
+ doneHandler(data.getData('Text'));
106
+ }
107
+ }
108
+
109
+ // Can't access clipboard -> don't call done handler
110
+ }
111
+
112
+ _showNotification(textKey) {
113
+ scout.create('DesktopNotification', {
114
+ parent: this.target,
115
+ severity: Status.Severity.WARNING,
116
+ message: this.target.session.text(textKey)
117
+ }).show();
118
+ }
119
+ }