@sapui5/sap.suite.ui.generic.template 1.139.0 → 1.139.2
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 +1 -1
- package/src/sap/suite/ui/generic/template/.library +1 -1
- package/src/sap/suite/ui/generic/template/AnalyticalListPage/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/Canvas/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/ListReport/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/controller/ControllerImplementation.js +1 -0
- package/src/sap/suite/ui/generic/template/ObjectPage/controller/RelatedAppsHandler.js +2 -0
- package/src/sap/suite/ui/generic/template/ObjectPage/controller/SectionTitleHandler.js +17 -13
- package/src/sap/suite/ui/generic/template/ObjectPage/controllerFrameworkExtensions.js +285 -270
- package/src/sap/suite/ui/generic/template/ObjectPage/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/ObjectPage/view/fragments/SmartForm.fragment.xml +2 -1
- package/src/sap/suite/ui/generic/template/QuickCreate/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/QuickView/manifest.json +1 -1
- package/src/sap/suite/ui/generic/template/js/AnnotationHelper.js +81 -0
- package/src/sap/suite/ui/generic/template/lib/AppComponent.js +16 -2
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFilterBarHandler.js +44 -198
- package/src/sap/suite/ui/generic/template/lib/ai/EasyFilterDataFetcherHelper.js +334 -0
- package/src/sap/suite/ui/generic/template/lib/navigation/NavigationController.js +6 -1
- package/src/sap/suite/ui/generic/template/lib/presentationControl/SmartTableHandler.js +1 -1
- package/src/sap/suite/ui/generic/template/library.js +1 -1
|
@@ -0,0 +1,334 @@
|
|
|
1
|
+
sap.ui.define([
|
|
2
|
+
'sap/ui/model/FilterOperator',
|
|
3
|
+
'sap/ui/model/Filter',
|
|
4
|
+
'sap/suite/ui/generic/template/js/AnnotationHelper'
|
|
5
|
+
], function(FilterOperator, Filter, AnnotationHelper) {
|
|
6
|
+
'use strict';
|
|
7
|
+
|
|
8
|
+
const mReverseOperator = {};
|
|
9
|
+
fillReverseOperator();
|
|
10
|
+
|
|
11
|
+
function fillReverseOperator() {
|
|
12
|
+
mReverseOperator[FilterOperator.EQ] = FilterOperator.NE;
|
|
13
|
+
mReverseOperator[FilterOperator.Contains] = FilterOperator.NotContains;
|
|
14
|
+
mReverseOperator[FilterOperator.EndsWith] = FilterOperator.NotEndsWith;
|
|
15
|
+
mReverseOperator[FilterOperator.StartsWith] = FilterOperator.NotStartsWith;
|
|
16
|
+
mReverseOperator[FilterOperator.BT] = FilterOperator.NB;
|
|
17
|
+
|
|
18
|
+
mReverseOperator[FilterOperator.NE] = FilterOperator.EQ;
|
|
19
|
+
mReverseOperator[FilterOperator.NotContains] = FilterOperator.Contains;
|
|
20
|
+
mReverseOperator[FilterOperator.NotEndsWith] = FilterOperator.EndsWith;
|
|
21
|
+
mReverseOperator[FilterOperator.NotStartsWith] = FilterOperator.StartsWith;
|
|
22
|
+
mReverseOperator[FilterOperator.NB] = FilterOperator.BT;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function getFiltersForDataFetching(sKey, keySpecificSelectedResult) {
|
|
26
|
+
const aFilters = [];
|
|
27
|
+
|
|
28
|
+
for (let i = 0; i < keySpecificSelectedResult.length; ++i) {
|
|
29
|
+
const result = keySpecificSelectedResult[i];
|
|
30
|
+
if (result.operator === FilterOperator.BT || result.operator === FilterOperator.NB) {
|
|
31
|
+
aFilters.push(new Filter({
|
|
32
|
+
path: sKey,
|
|
33
|
+
operator: result.operator,
|
|
34
|
+
value1: result.selectedValues[0],
|
|
35
|
+
value2: result.selectedValues[1]
|
|
36
|
+
}));
|
|
37
|
+
} else {
|
|
38
|
+
for (let j = 0; j < result.selectedValues.length; ++j) {
|
|
39
|
+
aFilters.push(new Filter({
|
|
40
|
+
path: sKey,
|
|
41
|
+
operator: result.operator,
|
|
42
|
+
value1: result.selectedValues[j]
|
|
43
|
+
}));
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
return aFilters;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isComparitveOperator(sFilter) {
|
|
51
|
+
return sFilter === FilterOperator.BT || sFilter === FilterOperator.NB ||
|
|
52
|
+
sFilter === FilterOperator.GT || sFilter === FilterOperator.LT ||
|
|
53
|
+
sFilter === FilterOperator.GE || sFilter === FilterOperator.LE;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function isNegationOperator(sOperator) {
|
|
57
|
+
return sOperator === FilterOperator.NE || sOperator === FilterOperator.NotContains ||
|
|
58
|
+
sOperator === FilterOperator.NotEndsWith || sOperator === FilterOperator.NotStartsWith ||
|
|
59
|
+
sOperator === FilterOperator.NB;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function getkeySpecifiedResultFromFilters(oFilter) {
|
|
63
|
+
let values = [];
|
|
64
|
+
if (oFilter.getOperator() === FilterOperator.BT || oFilter.getOperator() === FilterOperator.NB) {
|
|
65
|
+
values = [{
|
|
66
|
+
value: oFilter.getValue1(),
|
|
67
|
+
description: oFilter.getValue1()
|
|
68
|
+
},{
|
|
69
|
+
value: oFilter.getValue2(),
|
|
70
|
+
description: oFilter.getValue2()
|
|
71
|
+
}];
|
|
72
|
+
} else {
|
|
73
|
+
values.push({
|
|
74
|
+
value: oFilter.getValue1(),
|
|
75
|
+
description: oFilter.getValue1()
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return {
|
|
79
|
+
operator: oFilter.getOperator(),
|
|
80
|
+
selectedValues: values
|
|
81
|
+
};
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
async function fetchRecordsFromValueList(oValueList, oFilter, sKey, oSFBModel,oTemplateUtils) {
|
|
85
|
+
return new Promise((resolve, reject) => {
|
|
86
|
+
let oValueListDefaultBinding = oValueList[""];
|
|
87
|
+
let sValueListEntity = oValueListDefaultBinding.CollectionPath.String;
|
|
88
|
+
|
|
89
|
+
if (Array.isArray(oValueListDefaultBinding.Parameters)) {
|
|
90
|
+
let oParameter = oValueListDefaultBinding.Parameters.find(oParam => (oParam.RecordType === "com.sap.vocabularies.Common.v1.ValueListParameterInOut" && oParam.LocalDataProperty.PropertyPath === sKey));
|
|
91
|
+
if (oParameter) {
|
|
92
|
+
sKey = oParameter.ValueListProperty.String;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
let bIsNotOperator = isNegationOperator(oFilter.getOperator());
|
|
97
|
+
if (bIsNotOperator) {
|
|
98
|
+
oFilter = new Filter({
|
|
99
|
+
path: sKey,
|
|
100
|
+
operator: mReverseOperator[oFilter.getOperator()],
|
|
101
|
+
value1: oFilter.getValue1()
|
|
102
|
+
});
|
|
103
|
+
} else {
|
|
104
|
+
oFilter = new Filter({
|
|
105
|
+
path: sKey,
|
|
106
|
+
operator: oFilter.getOperator(),
|
|
107
|
+
value1: oFilter.getValue1()
|
|
108
|
+
});
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
oSFBModel.read("/" + sValueListEntity, {
|
|
112
|
+
filters: [oFilter],
|
|
113
|
+
success: async function(oResponse) {
|
|
114
|
+
const aResults = oResponse.results;
|
|
115
|
+
let aResponse;
|
|
116
|
+
try {
|
|
117
|
+
if (aResults.length > 0) {
|
|
118
|
+
aResponse = await getDescriptionForResponse(oResponse.results, sValueListEntity, sKey, oSFBModel,oTemplateUtils,oFilter);
|
|
119
|
+
} else {
|
|
120
|
+
aResponse = await fetchRecordsFromValueListUsingFuzzySearch(sValueListEntity, oFilter, sKey, oSFBModel,oTemplateUtils);
|
|
121
|
+
}
|
|
122
|
+
resolve({
|
|
123
|
+
operator: (bIsNotOperator) ? mReverseOperator[oFilter.getOperator()] : oFilter.getOperator(),
|
|
124
|
+
selectedValues: aResponse
|
|
125
|
+
});
|
|
126
|
+
} catch (error) {
|
|
127
|
+
reject(error);
|
|
128
|
+
}
|
|
129
|
+
},
|
|
130
|
+
error: async function(oError) {
|
|
131
|
+
//If the error is because of some property validation, we can try to do a fuzzy search
|
|
132
|
+
try {
|
|
133
|
+
const aResponse = await fetchRecordsFromValueListUsingFuzzySearch(sValueListEntity, oFilter, sKey, oSFBModel,oTemplateUtils);
|
|
134
|
+
resolve({
|
|
135
|
+
operator: (bIsNotOperator) ? mReverseOperator[oFilter.getOperator()] : oFilter.getOperator(),
|
|
136
|
+
selectedValues: aResponse
|
|
137
|
+
});
|
|
138
|
+
} catch (error) {
|
|
139
|
+
reject(error);
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
async function fetchRecordsFromValueListUsingFuzzySearch(sValueListEntity, oFilter, sKey, oSFBModel,oTemplateUtils) {
|
|
147
|
+
return new Promise((resolve, reject) => {
|
|
148
|
+
oSFBModel.read("/" + sValueListEntity, {
|
|
149
|
+
urlParameters: {
|
|
150
|
+
search: oFilter.getValue1()
|
|
151
|
+
},
|
|
152
|
+
success: async function(oResponse) {
|
|
153
|
+
let aResults = oResponse.results;
|
|
154
|
+
if (aResults.length == 0) {
|
|
155
|
+
//If it dosent have any records, the return the original value
|
|
156
|
+
resolve([{
|
|
157
|
+
value: oFilter.getValue1(),
|
|
158
|
+
description: oFilter.getValue1()
|
|
159
|
+
}]);
|
|
160
|
+
} else {
|
|
161
|
+
try {
|
|
162
|
+
aResults = await getDescriptionForResponse(aResults, sValueListEntity, sKey, oSFBModel,oTemplateUtils,oFilter);
|
|
163
|
+
resolve(aResults);
|
|
164
|
+
} catch (oError) {
|
|
165
|
+
reject(oError);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
},
|
|
169
|
+
error: function(oError) {
|
|
170
|
+
reject(oError);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function performClientSideSearch(aResults, oFilter,sKey,sDescriptionField) {
|
|
177
|
+
if (!sKey || !sDescriptionField) {
|
|
178
|
+
return aResults;
|
|
179
|
+
}
|
|
180
|
+
if (oFilter.getOperator() === FilterOperator.EQ || oFilter.getOperator() === FilterOperator.NE) {
|
|
181
|
+
const aFilteredResults = aResults.filter(result => result[sKey].toString().toLowerCase() === oFilter.getValue1().toString().toLowerCase() || result[sDescriptionField].toString().toLowerCase() === oFilter.getValue1().toString().toLowerCase());
|
|
182
|
+
return aFilteredResults.length > 0 ? aFilteredResults : aResults;
|
|
183
|
+
}
|
|
184
|
+
return aResults;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
async function getDescriptionForResponse(aResults, sValueListEntity, sKey, oSFBModel, oTemplateUtils,oFilter) {
|
|
188
|
+
return new Promise((resolve, reject) => {
|
|
189
|
+
const aResultsWithTextArrangement = [];
|
|
190
|
+
|
|
191
|
+
let oSFBMetaModel = oSFBModel.getMetaModel();
|
|
192
|
+
let oValueHelpEntity = oTemplateUtils.oCommonUtils.getMetaModelEntityType(sValueListEntity);
|
|
193
|
+
let oValueHelpProperty = oSFBMetaModel.getODataProperty(oValueHelpEntity, sKey);
|
|
194
|
+
let sTextArrangementPath = AnnotationHelper.getTextArrangementPath(oValueHelpProperty);
|
|
195
|
+
let sTextArrangement = AnnotationHelper.getTextArrangementForEasyFilter(oValueHelpEntity, oValueHelpProperty);
|
|
196
|
+
let navigationProperty = null;
|
|
197
|
+
let sEntityForTextDescription = sValueListEntity;
|
|
198
|
+
|
|
199
|
+
if (sTextArrangementPath && sTextArrangementPath.split("/").length > 1) {
|
|
200
|
+
navigationProperty = sTextArrangementPath.split("/")[0];
|
|
201
|
+
sEntityForTextDescription = oSFBMetaModel.getODataAssociationSetEnd(oValueHelpEntity, navigationProperty).entitySet;
|
|
202
|
+
sTextArrangementPath = sTextArrangementPath.split("/")[1];
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
aResults = performClientSideSearch(aResults, oFilter,sKey,sTextArrangementPath);
|
|
206
|
+
|
|
207
|
+
if (sValueListEntity === sEntityForTextDescription) {
|
|
208
|
+
aResults.forEach((result) => {
|
|
209
|
+
aResultsWithTextArrangement.push({
|
|
210
|
+
value: result[sKey],
|
|
211
|
+
description: AnnotationHelper.getTextArrangementFinalString(result, sKey, sTextArrangementPath, sTextArrangement)
|
|
212
|
+
});
|
|
213
|
+
});
|
|
214
|
+
resolve(aResultsWithTextArrangement);
|
|
215
|
+
} else {
|
|
216
|
+
let aAllMatchedResults = aResults.map((result) => {
|
|
217
|
+
return result[sKey];
|
|
218
|
+
});
|
|
219
|
+
let aMatchedFilterResults = aAllMatchedResults.map((matchedResult) => {
|
|
220
|
+
return new Filter({
|
|
221
|
+
path: sKey,
|
|
222
|
+
operator: FilterOperator.EQ,
|
|
223
|
+
value1: matchedResult
|
|
224
|
+
});
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// The below code only be executed when the textDescription and ValueList entities are different
|
|
228
|
+
oSFBModel.read("/" + sEntityForTextDescription, {
|
|
229
|
+
filters: aMatchedFilterResults,
|
|
230
|
+
success: function(oResponse) {
|
|
231
|
+
let aResults = oResponse.results;
|
|
232
|
+
|
|
233
|
+
aMatchedFilterResults.forEach((oFilter) => {
|
|
234
|
+
let result = aResults.find((res) => res[sKey] === oFilter.getValue1());
|
|
235
|
+
aResultsWithTextArrangement.push({
|
|
236
|
+
value: oFilter.getValue1(),
|
|
237
|
+
description: (result) ? AnnotationHelper.getTextArrangementFinalString(result, sKey, sTextArrangementPath, sTextArrangement) : oFilter.getValue1()
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
resolve(aResultsWithTextArrangement);
|
|
241
|
+
},
|
|
242
|
+
error: function(oError) {
|
|
243
|
+
reject(oError);
|
|
244
|
+
}
|
|
245
|
+
});
|
|
246
|
+
}
|
|
247
|
+
});
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* The primary responsibility of this function is to convert the given values into their corresponding
|
|
252
|
+
* IDs and Descriptions, respecting the defined TextArrangement.
|
|
253
|
+
*
|
|
254
|
+
* This process is carried out in two phases:
|
|
255
|
+
*
|
|
256
|
+
* Fetching the ID:
|
|
257
|
+
* 1) Make a $filter call using the provided value and operator.
|
|
258
|
+
* - If this returns results, we proceed to fetch the corresponding descriptions for these IDs.
|
|
259
|
+
* 2) If no results are returned, make a $search (fuzzy search) call instead.
|
|
260
|
+
*
|
|
261
|
+
* Fetching the Description:
|
|
262
|
+
* 1) The description is determined by the sap:text/com.sap.vocabularies.UI.v1.TextArrangement
|
|
263
|
+
* annotation of the ValueHelp entity, not from the main entity.
|
|
264
|
+
* 2) To fetch the description, we use a $filter call, since the IDs were already retrieved in the first step.
|
|
265
|
+
*
|
|
266
|
+
* Edge cases with negation operators:
|
|
267
|
+
* 1) For negation operators such as NB, NotStartsWith, etc., we make a batch call using the
|
|
268
|
+
* positive equivalent of these operators (e.g., BT, StartsWith).
|
|
269
|
+
* 2) After receiving the results, we return them with the original negation operator
|
|
270
|
+
* (e.g., NB, NotStartsWith).
|
|
271
|
+
*/
|
|
272
|
+
|
|
273
|
+
function fetchDataForKey(sKey, keySpecificSelectedResult, oState, oController, oTemplateUtils) {
|
|
274
|
+
return new Promise(async(resolve, reject) => {
|
|
275
|
+
try {
|
|
276
|
+
const aFinalResponse = [];
|
|
277
|
+
const oOwnerFilterControl = oState.oSmartFilterbar;
|
|
278
|
+
const sEntitySet = oController.getOwnerComponent().getEntitySet();
|
|
279
|
+
const oEntityType = oTemplateUtils.oCommonUtils.getMetaModelEntityType(sEntitySet);
|
|
280
|
+
const oSFBModel = oOwnerFilterControl.getModel();
|
|
281
|
+
const oSFBMetaModel = oSFBModel.getMetaModel();
|
|
282
|
+
const aFilters = getFiltersForDataFetching(sKey, keySpecificSelectedResult);
|
|
283
|
+
const oProperty = oSFBMetaModel.getODataProperty(oEntityType, sKey);
|
|
284
|
+
const sPropertyPath = oSFBMetaModel.getODataProperty(oEntityType, sKey, true);
|
|
285
|
+
const isValueHelpTableAvailable = AnnotationHelper.isValueHelpTableAvailable(oProperty);
|
|
286
|
+
|
|
287
|
+
if (!isValueHelpTableAvailable) {
|
|
288
|
+
// When there is no valueList associate, just return the original value in CodeList type
|
|
289
|
+
aFilters.forEach(oFilter => aFinalResponse.push(getkeySpecifiedResultFromFilters(oFilter)));
|
|
290
|
+
resolve(aFinalResponse);
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Get value help lists from metadata
|
|
294
|
+
const oPropertyContext = oSFBMetaModel.createBindingContext(sPropertyPath);
|
|
295
|
+
const oValueList = await oSFBMetaModel.getODataValueLists(oPropertyContext);
|
|
296
|
+
|
|
297
|
+
// Process each filter in parallel
|
|
298
|
+
const pPromises = [];
|
|
299
|
+
|
|
300
|
+
for (const oFilter of aFilters) {
|
|
301
|
+
if (isComparitveOperator(oFilter.getOperator())) {
|
|
302
|
+
// For comparative operators, just add the original values
|
|
303
|
+
aFinalResponse.push(getkeySpecifiedResultFromFilters(oFilter));
|
|
304
|
+
} else {
|
|
305
|
+
// For other operators, fetch records from value list
|
|
306
|
+
pPromises.push(fetchRecordsFromValueList(oValueList, oFilter, sKey, oSFBModel, oTemplateUtils));
|
|
307
|
+
}
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
// Wait for all promises to resolve
|
|
311
|
+
if (pPromises.length > 0) {
|
|
312
|
+
const aResponses = await Promise.all(pPromises);
|
|
313
|
+
aResponses.forEach(aResponse => {
|
|
314
|
+
aFinalResponse.push(aResponse);
|
|
315
|
+
});
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
resolve(aFinalResponse);
|
|
319
|
+
} catch (error) {
|
|
320
|
+
reject(error); // Re-throw to allow caller to handle
|
|
321
|
+
}
|
|
322
|
+
});
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
return {
|
|
326
|
+
fetchDataForKey: fetchDataForKey,
|
|
327
|
+
getFiltersForDataFetching: getFiltersForDataFetching,
|
|
328
|
+
isComparitveOperator: isComparitveOperator,
|
|
329
|
+
isNegationOperator: isNegationOperator,
|
|
330
|
+
getReverseOperator: function(operator) {
|
|
331
|
+
return mReverseOperator[operator];
|
|
332
|
+
}
|
|
333
|
+
};
|
|
334
|
+
});
|
|
@@ -900,6 +900,11 @@ sap.ui.define(["sap/ui/base/Object",
|
|
|
900
900
|
var oIntentAndAllAppStatesPromise = Promise.all(aIntentAppStatePromises);
|
|
901
901
|
var fnAdjustNavigationHierarchy = function(){
|
|
902
902
|
oTemplateContract.oShellServicePromise.then(function(oShellService){
|
|
903
|
+
// When the FE app is embedded within the Inbox app, the Inbox provides a stub implementation
|
|
904
|
+
// of the ShellUIService. In that case, the call to "oShellService.setHierarchy()" operates
|
|
905
|
+
// on this stub service (and not the actual ShellUIService).
|
|
906
|
+
//
|
|
907
|
+
// For more details, refer to AppComponent#fnGetShellUIServicePromise.
|
|
903
908
|
oShellService.setHierarchy([]);
|
|
904
909
|
oIntentAndAllAppStatesPromise.then(function(aIntent){
|
|
905
910
|
var sCurrentIntent = aIntent[0];
|
|
@@ -3223,7 +3228,7 @@ sap.ui.define(["sap/ui/base/Object",
|
|
|
3223
3228
|
* @param {sap.suite.ui.generic.template.lib.AppComponent} oAppComponent The AppComponent instance
|
|
3224
3229
|
* @public
|
|
3225
3230
|
* @extends sap.ui.base.Object
|
|
3226
|
-
* @version 1.139.
|
|
3231
|
+
* @version 1.139.2
|
|
3227
3232
|
* @since 1.30.0
|
|
3228
3233
|
* @alias sap.suite.ui.generic.template.lib.NavigationController
|
|
3229
3234
|
*/
|
|
@@ -582,7 +582,7 @@ sap.ui.define([
|
|
|
582
582
|
const getFieldProps = function (sField) {
|
|
583
583
|
return {
|
|
584
584
|
"sProperty": sField,
|
|
585
|
-
"sLabel": mPropertyByFieldName[sField]["sap:label"],
|
|
585
|
+
"sLabel": (mPropertyByFieldName[sField]["com.sap.vocabularies.Common.v1.Label"] || "").String || mPropertyByFieldName[sField]["sap:label"] || "",
|
|
586
586
|
"sValue": oItemContext.getProperty(sField),
|
|
587
587
|
"bHidden": !aVisibleColumns.has(sField)
|
|
588
588
|
};
|