forms-angular 0.12.0-beta.20 → 0.12.0-beta.201
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/client/forms-angular-bs-common.less +173 -4
- package/dist/client/forms-angular-bs2-specific.less +1 -1
- package/dist/client/forms-angular-bs3-specific.less +1 -1
- package/dist/client/forms-angular-with-bs2.css +2 -2
- package/dist/client/forms-angular-with-bs3.css +3 -3
- package/dist/client/forms-angular-with-bs3.less +1 -1
- package/dist/client/forms-angular.js +2185 -784
- package/dist/client/forms-angular.min.js +1 -1
- package/dist/client/index.d.ts +390 -115
- package/dist/server/data_form.js +1607 -936
- package/dist/server/index.d.ts +125 -0
- package/package.json +40 -41
- package/CHANGELOG.md +0 -255
|
@@ -4,22 +4,59 @@ var fng;
|
|
|
4
4
|
var directives;
|
|
5
5
|
(function (directives) {
|
|
6
6
|
/*@ngInject*/
|
|
7
|
-
|
|
7
|
+
modelControllerDropdown.$inject = ["securityService"];
|
|
8
|
+
function modelControllerDropdown(securityService) {
|
|
9
|
+
var menuVisibilityStr;
|
|
10
|
+
var menuDisabledStr;
|
|
11
|
+
var itemVisibilityStr = "isHidden($index)";
|
|
12
|
+
var itemDisabledStr = "ng-class=\"dropdownClass($index)\"";
|
|
13
|
+
// without a more fundamental re-write, we cannot support "instant" binding here, so we'll fall-back to using
|
|
14
|
+
// the next-best alternative, which is one-time binding
|
|
15
|
+
var oneTimeBinding = fng.formsAngular.elemSecurityFuncBinding !== "normal";
|
|
16
|
+
var bindingStr = oneTimeBinding ? "::" : "";
|
|
17
|
+
if (securityService.canDoSecurity("hidden")) {
|
|
18
|
+
menuVisibilityStr = "ng-if=\"contextMenuId && !contextMenuHidden\" ".concat(securityService.getHideableAttrs('{{ ::contextMenuId }}'));
|
|
19
|
+
if (oneTimeBinding) {
|
|
20
|
+
// because the isHidden(...) logic is highly likely to be model dependent, that cannot be one-time bound. to
|
|
21
|
+
// be able to combine one-time and regular binding, we'll use ng-if for the one-time bound stuff and ng-hide for the rest
|
|
22
|
+
itemVisibilityStr = "ng-if=\"::(choice.divider || !isSecurelyHidden(choice.id))\" ng-hide=\"".concat(itemVisibilityStr, "\"");
|
|
23
|
+
}
|
|
24
|
+
else if (fng.formsAngular.elemSecurityFuncBinding === "normal") {
|
|
25
|
+
itemVisibilityStr = "ng-hide=\"".concat(itemVisibilityStr, " || (!choice.divider && isSecurelyHidden(choice.id))\"");
|
|
26
|
+
}
|
|
27
|
+
itemVisibilityStr += " ".concat(securityService.getHideableAttrs('{{ ::choice.id }}'));
|
|
28
|
+
}
|
|
29
|
+
else {
|
|
30
|
+
menuVisibilityStr = "";
|
|
31
|
+
itemVisibilityStr = "ng-hide=\"".concat(itemVisibilityStr, "\"");
|
|
32
|
+
}
|
|
33
|
+
if (securityService.canDoSecurity("disabled")) {
|
|
34
|
+
menuDisabledStr = "disableable-link ng-disabled=\"contextMenuDisabled\" ".concat(securityService.getDisableableAttrs('{{ ::contextMenuId }}'));
|
|
35
|
+
// as ng-class is already being used, we'll add the .disabled class if the menu item is securely disabled using
|
|
36
|
+
// class="{{ }}". note that we "prevent" a disabled menu item from being clicked by checking for the DISABLED
|
|
37
|
+
// attribute in the doClick(...) function, and aborting if this is found.
|
|
38
|
+
// note that the 'normal' class introduced here might not actually do anything, but for one-time binding to work
|
|
39
|
+
// properly, we need a truthy value
|
|
40
|
+
itemDisabledStr += " class=\"{{ ".concat(bindingStr, "(!choice.divider && isSecurelyDisabled(choice.id)) ? 'disabled' : 'normal' }}\" ").concat(securityService.getDisableableAttrs('{{ ::choice.id }}'));
|
|
41
|
+
}
|
|
42
|
+
else {
|
|
43
|
+
menuDisabledStr = "";
|
|
44
|
+
}
|
|
8
45
|
return {
|
|
9
|
-
restrict:
|
|
46
|
+
restrict: "AE",
|
|
10
47
|
replace: true,
|
|
11
|
-
template:
|
|
12
|
-
' <a uib-dropdown-toggle>' +
|
|
48
|
+
template: "<li id=\"{{ contextMenuId }}\" ng-show=\"items.length > 0\" class=\"mcdd\" uib-dropdown ".concat(menuVisibilityStr, " ").concat(menuDisabledStr, ">") +
|
|
49
|
+
' <a href="#" uib-dropdown-toggle>' +
|
|
13
50
|
' {{contextMenu}} <b class="caret"></b>' +
|
|
14
|
-
|
|
51
|
+
" </a>" +
|
|
15
52
|
' <ul class="uib-dropdown-menu dropdown-menu">' +
|
|
16
|
-
|
|
17
|
-
' <a ng-show="choice.text" class="dropdown-option" ng-href="{{choice.url}}" ng-click="doClick($index, $event)">' +
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
53
|
+
" <li ng-repeat=\"choice in items\" ng-attr-id=\"{{choice.id}}\" ".concat(itemVisibilityStr, " ").concat(itemDisabledStr, ">") +
|
|
54
|
+
' <a ng-show="choice.text || choice.textFunc" class="dropdown-option" ng-href="{{choice.url || choice.urlFunc()}}" ng-click="doClick($index, $event)">' +
|
|
55
|
+
" {{ choice.text || choice.textFunc() }}" +
|
|
56
|
+
" </a>" +
|
|
57
|
+
" </li>" +
|
|
58
|
+
" </ul>" +
|
|
59
|
+
"</li>",
|
|
23
60
|
};
|
|
24
61
|
}
|
|
25
62
|
directives.modelControllerDropdown = modelControllerDropdown;
|
|
@@ -31,16 +68,11 @@ var fng;
|
|
|
31
68
|
var directives;
|
|
32
69
|
(function (directives) {
|
|
33
70
|
/*@ngInject*/
|
|
34
|
-
|
|
71
|
+
errorDisplay.$inject = ["cssFrameworkService"];
|
|
72
|
+
function errorDisplay(cssFrameworkService) {
|
|
35
73
|
return {
|
|
36
74
|
restrict: 'E',
|
|
37
|
-
|
|
38
|
-
' <div class="alert alert-error col-lg-offset-3 offset3 col-lg-6 col-xs-12 span6 alert-warning alert-dismissable">' +
|
|
39
|
-
' <button type="button" class="close" ng-click="dismissError()">×</button>' +
|
|
40
|
-
' <h4>{{alertTitle}}</h4>' +
|
|
41
|
-
' <div ng-bind-html="errorMessage"></div>' +
|
|
42
|
-
' </div>' +
|
|
43
|
-
'</div>'
|
|
75
|
+
templateUrl: 'error-display-' + cssFrameworkService.framework() + '.html'
|
|
44
76
|
};
|
|
45
77
|
}
|
|
46
78
|
directives.errorDisplay = errorDisplay;
|
|
@@ -48,6 +80,44 @@ var fng;
|
|
|
48
80
|
})(fng || (fng = {}));
|
|
49
81
|
/// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
|
|
50
82
|
var fng;
|
|
83
|
+
(function (fng) {
|
|
84
|
+
var controllers;
|
|
85
|
+
(function (controllers) {
|
|
86
|
+
/*@ngInject*/
|
|
87
|
+
LinkCtrl.$inject = ["$scope"];
|
|
88
|
+
function LinkCtrl($scope) {
|
|
89
|
+
/**
|
|
90
|
+
* In the event that the link is part of a form that belongs to a (ui-bootstrap) modal,
|
|
91
|
+
* close the modal
|
|
92
|
+
*/
|
|
93
|
+
$scope.checkNotModal = function () {
|
|
94
|
+
var elm = $scope.element[0];
|
|
95
|
+
var parentNode;
|
|
96
|
+
var finished = false;
|
|
97
|
+
var fakeEvt = {
|
|
98
|
+
preventDefault: angular.noop,
|
|
99
|
+
stopPropagation: angular.noop,
|
|
100
|
+
target: 1,
|
|
101
|
+
currentTarget: 1
|
|
102
|
+
};
|
|
103
|
+
do {
|
|
104
|
+
parentNode = elm.parentNode;
|
|
105
|
+
if (!parentNode) {
|
|
106
|
+
finished = true;
|
|
107
|
+
}
|
|
108
|
+
else if (typeof parentNode.getAttribute === "function" && parentNode.getAttribute('uib-modal-window')) {
|
|
109
|
+
angular.element(elm).scope().close(fakeEvt);
|
|
110
|
+
finished = true;
|
|
111
|
+
}
|
|
112
|
+
else {
|
|
113
|
+
elm = parentNode;
|
|
114
|
+
}
|
|
115
|
+
} while (!finished);
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
controllers.LinkCtrl = LinkCtrl;
|
|
119
|
+
})(controllers = fng.controllers || (fng.controllers = {}));
|
|
120
|
+
})(fng || (fng = {}));
|
|
51
121
|
(function (fng) {
|
|
52
122
|
var directives;
|
|
53
123
|
(function (directives) {
|
|
@@ -58,38 +128,124 @@ var fng;
|
|
|
58
128
|
restrict: 'E',
|
|
59
129
|
scope: { dataSrc: '&model' },
|
|
60
130
|
link: function (scope, element, attrs) {
|
|
61
|
-
var ref =
|
|
131
|
+
var ref = attrs['ref'];
|
|
132
|
+
var isLabel = attrs['text'] && (unescape(attrs['text']) !== attrs['text']);
|
|
62
133
|
var form = attrs['form'];
|
|
134
|
+
var linktab = attrs['linktab'];
|
|
63
135
|
scope['readonly'] = attrs['readonly'];
|
|
136
|
+
scope['element'] = element;
|
|
64
137
|
form = form ? form + '/' : '';
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
138
|
+
linktab = linktab ? '/' + linktab : '';
|
|
139
|
+
if (isLabel) {
|
|
140
|
+
var workScope = scope;
|
|
141
|
+
var workString = '';
|
|
142
|
+
while (typeof workScope['baseSchema'] !== "function" && workScope.$parent) {
|
|
143
|
+
if (typeof workScope['$index'] !== "undefined") {
|
|
144
|
+
throw new Error('No support for $index at this level - ' + workString);
|
|
145
|
+
}
|
|
146
|
+
workScope = workScope.$parent;
|
|
147
|
+
workString = workString + '$parent.';
|
|
148
|
+
}
|
|
149
|
+
var attrib = attrs['fld'];
|
|
150
|
+
var watchExpression;
|
|
151
|
+
var splitAttrib = attrib.split('.');
|
|
152
|
+
if (scope.$parent.subDoc && (scope.$parent.subDoc[attrib] || scope.$parent.subDoc[splitAttrib[splitAttrib.length - 1]])) {
|
|
153
|
+
// Support for use in directives in arrays
|
|
154
|
+
if (scope.$parent.subDoc[attrib]) {
|
|
155
|
+
watchExpression = workString + 'subDoc.' + attrib;
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
watchExpression = workString + 'subDoc.' + splitAttrib[splitAttrib.length - 1];
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
if (typeof workScope['$index'] !== "undefined") {
|
|
163
|
+
attrib = splitAttrib.pop();
|
|
164
|
+
attrib = splitAttrib.join('.') + '[' + workScope['$index'] + '].' + attrib;
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
attrib = '.' + attrib;
|
|
168
|
+
}
|
|
169
|
+
watchExpression = workString + 'record' + attrib;
|
|
170
|
+
}
|
|
171
|
+
scope.$watch(watchExpression, function (newVal) {
|
|
172
|
+
if (newVal) {
|
|
173
|
+
if (/^[a-f0-9]{24}/.test(newVal.toString())) {
|
|
174
|
+
newVal = newVal.slice(0, 24);
|
|
175
|
+
}
|
|
176
|
+
else if (newVal.id && /^[a-f0-9]{24}/.test(newVal.id)) {
|
|
177
|
+
newVal = newVal.id.slice(0, 24);
|
|
178
|
+
}
|
|
179
|
+
else if (scope.$parent["f_" + attrs['fld'] + "Options"]) {
|
|
180
|
+
// extract from lookups
|
|
181
|
+
var i = scope.$parent["f_" + attrs['fld'] + "Options"].indexOf(newVal);
|
|
182
|
+
if (i > -1) {
|
|
183
|
+
newVal = scope.$parent["f_" + attrs['fld'] + "_ids"][i];
|
|
80
184
|
}
|
|
81
185
|
else {
|
|
82
|
-
|
|
186
|
+
newVal = undefined;
|
|
83
187
|
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
|
|
188
|
+
}
|
|
189
|
+
else {
|
|
190
|
+
newVal = undefined;
|
|
191
|
+
}
|
|
192
|
+
if (newVal) {
|
|
193
|
+
scope['link'] = routingService.buildUrl(ref + '/' + form + (newVal.id || newVal) + '/edit' + linktab);
|
|
194
|
+
}
|
|
195
|
+
else {
|
|
196
|
+
scope['link'] = undefined;
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
else {
|
|
200
|
+
scope['link'] = undefined;
|
|
87
201
|
}
|
|
202
|
+
}, true);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
if (attrs['text'] && attrs['text'].length > 0) {
|
|
206
|
+
scope['text'] = attrs['text'];
|
|
88
207
|
}
|
|
89
|
-
|
|
208
|
+
var index = scope['$parent']['$index'];
|
|
209
|
+
scope.$watch('dataSrc()', function (newVal) {
|
|
210
|
+
if (newVal) {
|
|
211
|
+
if (typeof index !== 'undefined' && angular.isArray(newVal)) {
|
|
212
|
+
newVal = newVal[index];
|
|
213
|
+
}
|
|
214
|
+
scope['link'] = routingService.buildUrl(ref + '/' + form + newVal + '/edit' + linktab);
|
|
215
|
+
if (!scope['text']) {
|
|
216
|
+
SubmissionsService.getListAttributes(ref, newVal).then(function (response) {
|
|
217
|
+
var data = response.data;
|
|
218
|
+
if (data.success === false) {
|
|
219
|
+
scope['text'] = data.err;
|
|
220
|
+
}
|
|
221
|
+
else {
|
|
222
|
+
scope['text'] = data.list;
|
|
223
|
+
}
|
|
224
|
+
}, function (response) {
|
|
225
|
+
scope['text'] = 'Error ' + response.status + ': ' + response.data;
|
|
226
|
+
});
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}, true);
|
|
230
|
+
}
|
|
90
231
|
},
|
|
232
|
+
controller: "LinkCtrl",
|
|
91
233
|
template: function (element, attrs) {
|
|
92
|
-
|
|
234
|
+
function handleAnchor(contents) {
|
|
235
|
+
return "<a ng-click=\"checkNotModal()\" ng-href=\"{{ link || '#' }}\" class=\"fng-link\">".concat(contents, "</a>");
|
|
236
|
+
}
|
|
237
|
+
var retVal;
|
|
238
|
+
if (attrs.readonly) {
|
|
239
|
+
retVal = '<span class="fng-link">{{text}}</span>';
|
|
240
|
+
}
|
|
241
|
+
else if (attrs['text'] && unescape(attrs['text']) !== attrs['text']) {
|
|
242
|
+
retVal = handleAnchor(unescape(attrs['text']));
|
|
243
|
+
// retVal = '<a href="{{ link }}" class="fng-link">{{text}}</a>';
|
|
244
|
+
}
|
|
245
|
+
else {
|
|
246
|
+
retVal = handleAnchor('{{text}}');
|
|
247
|
+
}
|
|
248
|
+
return retVal;
|
|
93
249
|
}
|
|
94
250
|
};
|
|
95
251
|
}
|
|
@@ -117,7 +273,7 @@ var fng;
|
|
|
117
273
|
(function (fng) {
|
|
118
274
|
var directives;
|
|
119
275
|
(function (directives) {
|
|
120
|
-
formInput.$inject = ["$compile", "$rootScope", "$filter", "$timeout", "cssFrameworkService", "formGenerator", "formMarkupHelper"];
|
|
276
|
+
formInput.$inject = ["$compile", "$rootScope", "$filter", "$timeout", "cssFrameworkService", "formGenerator", "formMarkupHelper", "securityService"];
|
|
121
277
|
var tabsSetupState;
|
|
122
278
|
(function (tabsSetupState) {
|
|
123
279
|
tabsSetupState[tabsSetupState["Y"] = 0] = "Y";
|
|
@@ -125,7 +281,7 @@ var fng;
|
|
|
125
281
|
tabsSetupState[tabsSetupState["Forced"] = 2] = "Forced";
|
|
126
282
|
})(tabsSetupState || (tabsSetupState = {}));
|
|
127
283
|
/*@ngInject*/
|
|
128
|
-
function formInput($compile, $rootScope, $filter, $timeout, cssFrameworkService, formGenerator, formMarkupHelper) {
|
|
284
|
+
function formInput($compile, $rootScope, $filter, $timeout, cssFrameworkService, formGenerator, formMarkupHelper, securityService) {
|
|
129
285
|
return {
|
|
130
286
|
restrict: 'EA',
|
|
131
287
|
link: function (scope, element, attrs) {
|
|
@@ -146,7 +302,7 @@ var fng;
|
|
|
146
302
|
// <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
|
|
147
303
|
// </div>
|
|
148
304
|
//
|
|
149
|
-
// Inline
|
|
305
|
+
// Inline or stacked
|
|
150
306
|
// <div class="form-group">
|
|
151
307
|
// <label class="sr-only" for="exampleInputEmail2">Email address</label>
|
|
152
308
|
// <input type="email" class="form-control" id="exampleInputEmail2" placeholder="Enter email">
|
|
@@ -165,11 +321,11 @@ var fng;
|
|
|
165
321
|
// <input type="text" placeholder="Type something…">
|
|
166
322
|
// <span class="help-block">Example block-level help text here.</span>
|
|
167
323
|
//
|
|
168
|
-
// Inline
|
|
324
|
+
// Inline or Stacked
|
|
169
325
|
// <input type="text" class="input-small" placeholder="Email">
|
|
170
326
|
var subkeys = [];
|
|
171
327
|
var tabsSetup = tabsSetupState.N;
|
|
172
|
-
var generateInput = function (fieldInfo, modelString, isRequired,
|
|
328
|
+
var generateInput = function (fieldInfo, modelString, isRequired, options) {
|
|
173
329
|
function generateEnumInstructions() {
|
|
174
330
|
var enumInstruction;
|
|
175
331
|
if (angular.isArray(scope[fieldInfo.options])) {
|
|
@@ -195,6 +351,10 @@ var fng;
|
|
|
195
351
|
}
|
|
196
352
|
return enumInstruction;
|
|
197
353
|
}
|
|
354
|
+
var idString = fieldInfo.id;
|
|
355
|
+
if (fieldInfo.array || options.subschema) {
|
|
356
|
+
idString = formMarkupHelper.generateArrayElementIdString(idString, fieldInfo, options);
|
|
357
|
+
}
|
|
198
358
|
var nameString;
|
|
199
359
|
if (!modelString) {
|
|
200
360
|
var modelBase = (options.model || 'record') + '.';
|
|
@@ -206,7 +366,7 @@ var fng;
|
|
|
206
366
|
var lastPart = compoundName.slice(root.length + 1);
|
|
207
367
|
if (options.index) {
|
|
208
368
|
modelString += root + '[' + options.index + '].' + lastPart;
|
|
209
|
-
idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[
|
|
369
|
+
idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[|]\.)/g, '-');
|
|
210
370
|
}
|
|
211
371
|
else {
|
|
212
372
|
modelString += root;
|
|
@@ -216,7 +376,6 @@ var fng;
|
|
|
216
376
|
}
|
|
217
377
|
else {
|
|
218
378
|
modelString += '[$index].' + lastPart;
|
|
219
|
-
idString = null;
|
|
220
379
|
nameString = compoundName.replace(/\./g, '-');
|
|
221
380
|
}
|
|
222
381
|
}
|
|
@@ -228,9 +387,8 @@ var fng;
|
|
|
228
387
|
var allInputsVars = formMarkupHelper.allInputsVars(scope, fieldInfo, options, modelString, idString, nameString);
|
|
229
388
|
var common = allInputsVars.common;
|
|
230
389
|
var value;
|
|
231
|
-
var requiredStr = (isRequired || fieldInfo.required) ? ' required' : '';
|
|
232
390
|
isRequired = isRequired || fieldInfo.required;
|
|
233
|
-
var requiredStr = isRequired ? ' required' : '';
|
|
391
|
+
var requiredStr = isRequired ? ' required ' : '';
|
|
234
392
|
var enumInstruction;
|
|
235
393
|
switch (fieldInfo.type) {
|
|
236
394
|
case 'select':
|
|
@@ -238,8 +396,9 @@ var fng;
|
|
|
238
396
|
value = '<input placeholder="fng-select2 has been removed" readonly>';
|
|
239
397
|
}
|
|
240
398
|
else {
|
|
241
|
-
common += (fieldInfo.
|
|
399
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
242
400
|
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
401
|
+
common += " aria-label=\"".concat(fieldInfo.label && fieldInfo.label !== "" ? fieldInfo.label : fieldInfo.name, "\" ");
|
|
243
402
|
value = '<select ' + common + 'class="' + allInputsVars.formControl.trim() + allInputsVars.compactClass + allInputsVars.sizeClassBS2 + '" ' + requiredStr + '>';
|
|
244
403
|
if (!isRequired) {
|
|
245
404
|
value += '<option></option>';
|
|
@@ -268,25 +427,32 @@ var fng;
|
|
|
268
427
|
}
|
|
269
428
|
break;
|
|
270
429
|
case 'link':
|
|
271
|
-
|
|
272
|
-
fieldInfo.ref = { type: 'lookup', collection: fieldInfo.ref };
|
|
273
|
-
console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(fieldInfo.ref));
|
|
274
|
-
}
|
|
275
|
-
value = '<fng-link model="' + modelString + '" ref="' + JSON.stringify(fieldInfo.ref).replace(/"/g, '"') + '"';
|
|
430
|
+
value = '<fng-link model="' + modelString + '" ref="' + fieldInfo.ref + '"';
|
|
276
431
|
if (fieldInfo.form) {
|
|
277
432
|
value += ' form="' + fieldInfo.form + '"';
|
|
278
433
|
}
|
|
279
|
-
if (fieldInfo.
|
|
280
|
-
value += '
|
|
434
|
+
if (fieldInfo.linktab) {
|
|
435
|
+
value += ' linktab="' + fieldInfo.linktab + '"';
|
|
436
|
+
}
|
|
437
|
+
if (fieldInfo.linktext) {
|
|
438
|
+
value += ' text="' + fieldInfo.linktext + '"';
|
|
281
439
|
}
|
|
282
440
|
if (fieldInfo.readonly) {
|
|
283
|
-
|
|
441
|
+
if (typeof fieldInfo.readonly === "boolean") {
|
|
442
|
+
value += " readonly=\"true\"";
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
value += " ng-readonly=\"".concat(fieldInfo.readonly, "\"");
|
|
446
|
+
}
|
|
284
447
|
}
|
|
285
448
|
value += '></fng-link>';
|
|
286
449
|
break;
|
|
287
450
|
case 'radio':
|
|
288
451
|
value = '';
|
|
289
|
-
common += requiredStr
|
|
452
|
+
common += requiredStr;
|
|
453
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
454
|
+
;
|
|
455
|
+
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
290
456
|
var separateLines = options.formstyle === 'vertical' || (options.formstyle !== 'inline' && !fieldInfo.inlineRadio);
|
|
291
457
|
if (angular.isArray(fieldInfo.options)) {
|
|
292
458
|
if (options.subschema) {
|
|
@@ -296,8 +462,7 @@ var fng;
|
|
|
296
462
|
var thisCommon_1;
|
|
297
463
|
angular.forEach(fieldInfo.options, function (optValue, idx) {
|
|
298
464
|
thisCommon_1 = common.replace('id="', 'id="' + idx + '-');
|
|
299
|
-
value +=
|
|
300
|
-
value += ' value="' + optValue + '">' + optValue;
|
|
465
|
+
value += "<input ".concat(thisCommon_1, " type=\"radio\" aria-label=\"").concat(optValue, "\" value=\"").concat(optValue, "\">").concat(optValue);
|
|
301
466
|
if (separateLines) {
|
|
302
467
|
value += '<br />';
|
|
303
468
|
}
|
|
@@ -312,22 +477,23 @@ var fng;
|
|
|
312
477
|
}
|
|
313
478
|
enumInstruction = generateEnumInstructions();
|
|
314
479
|
value += '<' + tagType + ' ng-repeat="option in ' + enumInstruction.repeat + '">';
|
|
315
|
-
value +=
|
|
316
|
-
value += enumInstruction.label || enumInstruction.value;
|
|
317
|
-
value += ' }} </' + tagType + '> ';
|
|
480
|
+
value += "<input ".concat(common.replace('id="', 'id="{{$index}}-'), " type=\"radio\" aria-label=\"").concat(enumInstruction.value, "\" value=\"{{ ").concat(enumInstruction.value, " }}\"> {{ ").concat(enumInstruction.label || enumInstruction.value, " }} </").concat(tagType, "> ");
|
|
318
481
|
}
|
|
319
482
|
break;
|
|
320
483
|
case 'checkbox':
|
|
321
|
-
common += requiredStr
|
|
484
|
+
common += requiredStr;
|
|
485
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
486
|
+
;
|
|
487
|
+
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
488
|
+
value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
|
|
322
489
|
if (cssFrameworkService.framework() === 'bs3') {
|
|
323
|
-
value = '<div class="checkbox"
|
|
324
|
-
}
|
|
325
|
-
else {
|
|
326
|
-
value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
|
|
490
|
+
value = '<div class="checkbox">' + value + '</div>';
|
|
327
491
|
}
|
|
328
492
|
break;
|
|
329
493
|
default:
|
|
330
494
|
common += formMarkupHelper.addTextInputMarkup(allInputsVars, fieldInfo, requiredStr);
|
|
495
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
496
|
+
;
|
|
331
497
|
if (fieldInfo.type === 'textarea') {
|
|
332
498
|
if (fieldInfo.rows) {
|
|
333
499
|
if (fieldInfo.rows === 'auto') {
|
|
@@ -344,7 +510,7 @@ var fng;
|
|
|
344
510
|
allInputsVars.sizeClassBS3 = 'col-xs-12';
|
|
345
511
|
}
|
|
346
512
|
}
|
|
347
|
-
value = '<textarea ' + common + '
|
|
513
|
+
value = '<textarea ' + common + '></textarea>';
|
|
348
514
|
}
|
|
349
515
|
else {
|
|
350
516
|
value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
|
|
@@ -364,6 +530,9 @@ var fng;
|
|
|
364
530
|
case 'inline':
|
|
365
531
|
result = 'form-inline';
|
|
366
532
|
break;
|
|
533
|
+
case 'stacked':
|
|
534
|
+
result = 'form-stacked';
|
|
535
|
+
break;
|
|
367
536
|
case 'horizontalCompact':
|
|
368
537
|
result = 'form-horizontal compact';
|
|
369
538
|
break;
|
|
@@ -374,11 +543,12 @@ var fng;
|
|
|
374
543
|
return result;
|
|
375
544
|
};
|
|
376
545
|
var containerInstructions = function (info) {
|
|
377
|
-
var result
|
|
546
|
+
var result;
|
|
378
547
|
if (typeof info.containerType === 'function') {
|
|
379
548
|
result = info.containerType(info);
|
|
380
549
|
}
|
|
381
550
|
else {
|
|
551
|
+
result = {};
|
|
382
552
|
switch (info.containerType) {
|
|
383
553
|
case 'tab':
|
|
384
554
|
var tabNo = -1;
|
|
@@ -391,12 +561,26 @@ var fng;
|
|
|
391
561
|
if (tabNo >= 0) {
|
|
392
562
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
393
563
|
// result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"'
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
564
|
+
var idStr = "".concat(_.camelCase(info.title), "Tab");
|
|
565
|
+
var visibility = securityService.considerVisibility(idStr, scope);
|
|
566
|
+
if (visibility.omit) {
|
|
567
|
+
// we already know this field should be invisible, so we needn't add anything for it
|
|
568
|
+
result.omit = true;
|
|
569
|
+
}
|
|
570
|
+
else {
|
|
571
|
+
var attrs_1 = "id=\"".concat(idStr, "\"");
|
|
572
|
+
if (visibility.visibilityAttr) {
|
|
573
|
+
// an angular expression to determine the visibility of this field later...
|
|
574
|
+
attrs_1 += " ".concat(visibility.visibilityAttr);
|
|
575
|
+
}
|
|
576
|
+
attrs_1 += securityService.generateDisabledAttr(idStr, scope, { attr: "disable", attrRequiresValue: true }); // uib-tab expects 'disable="true"` rather than 'disabled="true"' or just disabled
|
|
577
|
+
result.before = "<uib-tab ".concat(attrs_1, " deselect=\"tabDeselect($event, $selectedIndex)\" select=\"updateQueryForTab('").concat(info.title, "')\" heading=\"").concat(info.title, "\"");
|
|
578
|
+
if (tabNo > 0) {
|
|
579
|
+
result.before += 'active="tabs[' + tabNo + '].active"';
|
|
580
|
+
}
|
|
581
|
+
result.before += '>';
|
|
582
|
+
result.after = '</uib-tab>';
|
|
397
583
|
}
|
|
398
|
-
result.before += '>';
|
|
399
|
-
result.after = '</uib-tab>';
|
|
400
584
|
}
|
|
401
585
|
else {
|
|
402
586
|
result.before = '<p>Error! Tab ' + info.title + ' not found in tab list</p>';
|
|
@@ -452,8 +636,46 @@ var fng;
|
|
|
452
636
|
}
|
|
453
637
|
return result;
|
|
454
638
|
};
|
|
639
|
+
var generateInlineHeaders = function (instructionsArray, options, model, evenWhenEmpty) {
|
|
640
|
+
// "column" headers for nested schemas that use formStyle: "inline" will only line up with their respective
|
|
641
|
+
// controls when widths are applied to both the cg_f_xxxx and col_label_xxxx element using css.
|
|
642
|
+
// Likely, the widths will need to be the same, so consider using the following:
|
|
643
|
+
// div[id$="_f_<collection>_<field>"] {
|
|
644
|
+
// width: 100px;
|
|
645
|
+
// }
|
|
646
|
+
// one column can grow to the remaining available width thus:
|
|
647
|
+
// div[id$="_f_<collection>_<field>"] {
|
|
648
|
+
// flex-grow: 1;
|
|
649
|
+
// }
|
|
650
|
+
var hideWhenEmpty = evenWhenEmpty ? "" : "ng-hide=\"!".concat(model, " || ").concat(model, ".length === 0\"");
|
|
651
|
+
var res = "<div class=\"inline-col-headers\" style=\"display:flex\" ".concat(hideWhenEmpty, ">");
|
|
652
|
+
for (var _i = 0, instructionsArray_1 = instructionsArray; _i < instructionsArray_1.length; _i++) {
|
|
653
|
+
var info = instructionsArray_1[_i];
|
|
654
|
+
// need to call this now to ensure the id is set. will probably be (harmlessly) called again later.
|
|
655
|
+
inferMissingProperties(info, options);
|
|
656
|
+
res += '<div ';
|
|
657
|
+
info.showWhen = info.showWhen || info.showwhen; // deal with use within a directive
|
|
658
|
+
if (info.showWhen) {
|
|
659
|
+
if (typeof info.showWhen === 'string') {
|
|
660
|
+
res += 'ng-show="' + info.showWhen + '"';
|
|
661
|
+
}
|
|
662
|
+
else {
|
|
663
|
+
res += 'ng-show="' + formMarkupHelper.generateNgShow(info.showWhen, model) + '"';
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
if (info.id && typeof info.id.replace === "function") {
|
|
667
|
+
res += ' id="col_label_' + info.id.replace(/\./g, '-') + '"';
|
|
668
|
+
}
|
|
669
|
+
res += " class=\"inline-col-header\"><label for=\"".concat(info.id, "\" class=\"control-label\">").concat(info.label, "</label></div>");
|
|
670
|
+
}
|
|
671
|
+
res += "</div>";
|
|
672
|
+
return res;
|
|
673
|
+
};
|
|
455
674
|
var handleField = function (info, options) {
|
|
456
675
|
var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
|
|
676
|
+
if (fieldChrome.omit) {
|
|
677
|
+
return "";
|
|
678
|
+
}
|
|
457
679
|
var template = fieldChrome.template;
|
|
458
680
|
if (info.schema) {
|
|
459
681
|
var niceName = info.name.replace(/\./g, '_');
|
|
@@ -474,7 +696,8 @@ var fng;
|
|
|
474
696
|
formstyle: options.formstyle,
|
|
475
697
|
subkey: schemaDefName + '_subkey',
|
|
476
698
|
subkeyno: arraySel,
|
|
477
|
-
subschemaroot: info.name
|
|
699
|
+
subschemaroot: info.name,
|
|
700
|
+
suppressNestingWarning: info.suppressNestingWarning
|
|
478
701
|
});
|
|
479
702
|
template += topAndTail.after;
|
|
480
703
|
}
|
|
@@ -482,7 +705,9 @@ var fng;
|
|
|
482
705
|
}
|
|
483
706
|
else {
|
|
484
707
|
if (options.subschema) {
|
|
485
|
-
|
|
708
|
+
if (!options.suppressNestingWarning) {
|
|
709
|
+
console.log('Attempts at supporting deep nesting have been removed - will hopefully be re-introduced at a later date');
|
|
710
|
+
}
|
|
486
711
|
}
|
|
487
712
|
else {
|
|
488
713
|
var model = (options.model || 'record') + '.' + info.name;
|
|
@@ -504,20 +729,47 @@ var fng;
|
|
|
504
729
|
}
|
|
505
730
|
}
|
|
506
731
|
/* Array body */
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
732
|
+
if (info.formStyle === "inline" && info.inlineHeaders) {
|
|
733
|
+
template += generateInlineHeaders(info.schema, options, model, info.inlineHeaders === "always");
|
|
734
|
+
}
|
|
735
|
+
// handleReadOnlyDisabled() returns two strings - the 'disabled' attribute(s), and the 'disableable'
|
|
736
|
+
// attributes. for the purpose of deciding if / how to disable sorting if the list itself is
|
|
737
|
+
// disabled, we're only interested in the former...
|
|
738
|
+
var disableCond = formMarkupHelper.handleReadOnlyDisabled(info, scope)[0];
|
|
739
|
+
// if we already know that the field is disabled (only possible when formsAngular.elemSecurityFuncBinding === "instant")
|
|
740
|
+
// then we don't need to add the sortable attribute at all
|
|
741
|
+
// otherwise, we need to include the ng-disabled on the <ol> so this can be referenced by the ui-sortable directive
|
|
742
|
+
// (see sortableOptions)
|
|
743
|
+
var sortableStr = info.sortable && disableCond.trim().toLowerCase() !== "disabled"
|
|
744
|
+
? "".concat(disableCond, " ui-sortable=\"sortableOptions\" ng-model=\"").concat(model, "\"")
|
|
745
|
+
: "";
|
|
746
|
+
template += "<ol class=\"sub-doc\" ".concat(sortableStr, ">");
|
|
747
|
+
// if a "disabled + children" DOM effect is applied to the list, this should serve to disable all of the
|
|
748
|
+
// fields in the list sub-schema, along with the remove and add buttons for this list. the following
|
|
749
|
+
// string will be added to the list items and the add and remove buttons to identify this fact.
|
|
750
|
+
var disableableAncestorStr = formMarkupHelper.genDisableableAncestorStr(info.id);
|
|
751
|
+
template +=
|
|
752
|
+
"<li ng-form id=\"".concat(info.id, "List_{{$index}}\" name=\"form_").concat(niceName, "{{$index}}\" ").concat(disableableAncestorStr) +
|
|
753
|
+
" class=\"".concat(convertFormStyleToClass(info.formStyle)) +
|
|
754
|
+
" ".concat(cssFrameworkService.framework() === 'bs2' ? 'row-fluid' : '') +
|
|
755
|
+
" ".concat(info.inlineHeaders ? 'width-controlled' : '') +
|
|
756
|
+
" ".concat(info.ngClass ? "ng-class:" + info.ngClass : '', "\"") +
|
|
757
|
+
" ng-repeat=\"subDoc in ".concat(model, " track by $index\"") +
|
|
758
|
+
" ".concat(info.filterable ? 'data-ng-hide="subDoc._hidden"' : '') +
|
|
759
|
+
">";
|
|
511
760
|
if (cssFrameworkService.framework() === 'bs2') {
|
|
512
761
|
template += '<div class="row-fluid sub-doc">';
|
|
513
762
|
}
|
|
514
|
-
if (
|
|
515
|
-
|
|
763
|
+
if (info.noRemove !== true || info.customSubDoc) {
|
|
764
|
+
// we need to put disableableAncestorStr on the div rather than on the remove button because the
|
|
765
|
+
// way that is styled means that any coloured outlines that might be added when "Identify page elements" is on
|
|
766
|
+
// will be masked
|
|
767
|
+
template += " <div class=\"sub-doc-btns\" ".concat(info.noRemove !== true ? disableableAncestorStr : "", ">");
|
|
516
768
|
if (typeof info.customSubDoc == 'string') {
|
|
517
769
|
template += info.customSubDoc;
|
|
518
770
|
}
|
|
519
771
|
if (info.noRemove !== true) {
|
|
520
|
-
template += "<button "
|
|
772
|
+
template += "<button ".concat(disableCond, " ").concat(info.noRemove ? 'ng-hide="' + info.noRemove + '"' : '', " name=\"remove_").concat(info.id, "_btn\" ng-click=\"remove('").concat(info.name, "', $index, $event)\"");
|
|
521
773
|
if (info.remove) {
|
|
522
774
|
template += ' class="remove-btn btn btn-mini btn-default btn-xs form-btn"><i class="' + formMarkupHelper.glyphClass() + '-minus"></i> Remove';
|
|
523
775
|
}
|
|
@@ -534,31 +786,69 @@ var fng;
|
|
|
534
786
|
}
|
|
535
787
|
template += '</div> ';
|
|
536
788
|
}
|
|
789
|
+
var parts = void 0;
|
|
790
|
+
if (info.subDocContainerType) {
|
|
791
|
+
var containerType = scope[info.subDocContainerType] || info.subDocContainerType;
|
|
792
|
+
var containerProps = Object.assign({ containerType: containerType }, info.subDocContainerProps);
|
|
793
|
+
parts = containerInstructions(containerProps);
|
|
794
|
+
}
|
|
795
|
+
if (parts === null || parts === void 0 ? void 0 : parts.before) {
|
|
796
|
+
template += parts.before;
|
|
797
|
+
}
|
|
537
798
|
template += processInstructions(info.schema, false, {
|
|
538
799
|
subschema: 'true',
|
|
539
800
|
formstyle: info.formStyle,
|
|
540
801
|
model: options.model,
|
|
541
|
-
subschemaroot: info.name
|
|
802
|
+
subschemaroot: info.name,
|
|
803
|
+
suppressNestingWarning: info.suppressNestingWarning
|
|
542
804
|
});
|
|
805
|
+
if (parts === null || parts === void 0 ? void 0 : parts.after) {
|
|
806
|
+
template += parts.after;
|
|
807
|
+
}
|
|
543
808
|
if (cssFrameworkService.framework() === 'bs2') {
|
|
544
809
|
template += ' </div>';
|
|
545
810
|
}
|
|
546
811
|
template += '</li>';
|
|
547
812
|
template += '</ol>';
|
|
548
813
|
/* Array footer */
|
|
549
|
-
if (info.noAdd !== true || typeof info.customFooter == 'string') {
|
|
814
|
+
if (info.noAdd !== true || typeof info.customFooter == 'string' || info.noneIndicator) {
|
|
550
815
|
var footer = '';
|
|
551
816
|
if (typeof info.customFooter == 'string') {
|
|
552
817
|
footer = info.customFooter;
|
|
553
818
|
}
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
template += '<div class="row schema-foot"><div class="col-sm-offset-3">' + footer + '</div></div>';
|
|
819
|
+
var hideCond = '';
|
|
820
|
+
var indicatorShowCond = "".concat(model, ".length == 0");
|
|
821
|
+
if (info.noAdd === true) {
|
|
822
|
+
indicatorShowCond = "ng-show=\"".concat(indicatorShowCond, "\"");
|
|
559
823
|
}
|
|
560
824
|
else {
|
|
561
|
-
|
|
825
|
+
hideCond = info.noAdd ? "ng-hide=\"".concat(info.noAdd, "\"") : '';
|
|
826
|
+
indicatorShowCond = info.noAdd ? "ng-show=\"".concat(info.noAdd, " && ").concat(indicatorShowCond, "\"") : '';
|
|
827
|
+
// we need the button to have disableCond (to actually disable it, if the list is disabled)
|
|
828
|
+
// adding disableableAncestorStr seems more correct than for it to have the disableable attribute
|
|
829
|
+
footer +=
|
|
830
|
+
"<button ".concat(hideCond, " ").concat(disableCond, " ").concat(disableableAncestorStr, " id=\"add_").concat(info.id, "_btn\" class=\"add-btn btn btn-default btn-xs btn-mini\" ng-click=\"add('").concat(info.name, "',$event)\">") +
|
|
831
|
+
" <i class=\"".concat(formMarkupHelper.glyphClass(), "-plus\"></i> Add ") +
|
|
832
|
+
"</button>";
|
|
833
|
+
}
|
|
834
|
+
if (info.noneIndicator) {
|
|
835
|
+
footer += "<span ".concat(indicatorShowCond, " class=\"none_indicator\" id=\"no_").concat(info.id, "_indicator\">None</span>");
|
|
836
|
+
// hideCond for the schema-foot is if there's no add button and no indicator
|
|
837
|
+
hideCond = "".concat(model, ".length > 0");
|
|
838
|
+
if (info.noAdd === true) {
|
|
839
|
+
hideCond = "ng-hide=\"".concat(hideCond, "\"");
|
|
840
|
+
}
|
|
841
|
+
else {
|
|
842
|
+
hideCond = info.noAdd ? "ng-hide=\"".concat(info.noAdd, " && ").concat(hideCond, "\"") : '';
|
|
843
|
+
}
|
|
844
|
+
}
|
|
845
|
+
if (footer !== '') {
|
|
846
|
+
if (cssFrameworkService.framework() === 'bs3') {
|
|
847
|
+
template += "<div ".concat(hideCond, " class=\"row schema-foot\"><div class=\"col-sm-offset-3\">").concat(footer, "</div></div>");
|
|
848
|
+
}
|
|
849
|
+
else {
|
|
850
|
+
template += "<div ".concat(hideCond, " class = \"schema-foot \">").concat(footer, "</div>");
|
|
851
|
+
}
|
|
562
852
|
}
|
|
563
853
|
}
|
|
564
854
|
}
|
|
@@ -570,34 +860,40 @@ var fng;
|
|
|
570
860
|
var controlDivClasses = formMarkupHelper.controlDivClasses(options);
|
|
571
861
|
if (info.array) {
|
|
572
862
|
controlDivClasses.push('fng-array');
|
|
573
|
-
if (options.formstyle === 'inline') {
|
|
574
|
-
throw new Error('Cannot use arrays in an inline form');
|
|
863
|
+
if (options.formstyle === 'inline' || options.formstyle === 'stacked') {
|
|
864
|
+
throw new Error('Cannot use arrays in an inline or stacked form');
|
|
575
865
|
}
|
|
576
866
|
template += formMarkupHelper.label(scope, info, info.type !== 'link', options);
|
|
577
|
-
|
|
867
|
+
var stashedHelp = info.help;
|
|
868
|
+
delete info.help;
|
|
869
|
+
var inputHtml = generateInput(info, info.type === 'link' ? null : 'arrayItem.x', true, options);
|
|
870
|
+
info.help = stashedHelp;
|
|
871
|
+
template += formMarkupHelper.handleArrayInputAndControlDiv(inputHtml, controlDivClasses, scope, info, options);
|
|
578
872
|
}
|
|
579
873
|
else {
|
|
580
874
|
// Single fields here
|
|
581
875
|
template += formMarkupHelper.label(scope, info, null, options);
|
|
582
|
-
|
|
583
|
-
console.log("********* Options required - found it ********");
|
|
584
|
-
}
|
|
585
|
-
template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, info.id, options), controlDivClasses);
|
|
876
|
+
template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, options), controlDivClasses);
|
|
586
877
|
}
|
|
587
878
|
}
|
|
588
879
|
template += fieldChrome.closeTag;
|
|
589
880
|
return template;
|
|
590
881
|
};
|
|
591
|
-
var inferMissingProperties = function (info) {
|
|
882
|
+
var inferMissingProperties = function (info, options) {
|
|
592
883
|
// infer missing values
|
|
593
884
|
info.type = info.type || 'text';
|
|
594
885
|
if (info.id) {
|
|
595
|
-
if (typeof info.id === 'number' ||
|
|
886
|
+
if (typeof info.id === 'number' || info.id.match(/^[0-9]/)) {
|
|
596
887
|
info.id = '_' + info.id;
|
|
597
888
|
}
|
|
598
889
|
}
|
|
599
890
|
else {
|
|
600
|
-
|
|
891
|
+
if (options && options.noid) {
|
|
892
|
+
info.id = null;
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
info.id = 'f_' + info.name.replace(/\./g, '_');
|
|
896
|
+
}
|
|
601
897
|
}
|
|
602
898
|
info.label = (info.label !== undefined) ? (info.label === null ? '' : info.label) : $filter('titleCase')(info.name.split('.').slice(-1)[0]);
|
|
603
899
|
};
|
|
@@ -608,7 +904,11 @@ var fng;
|
|
|
608
904
|
if (instructionsArray) {
|
|
609
905
|
for (var anInstruction = 0; anInstruction < instructionsArray.length; anInstruction++) {
|
|
610
906
|
var info = instructionsArray[anInstruction];
|
|
611
|
-
if (
|
|
907
|
+
if (options.viewform) {
|
|
908
|
+
info = angular.copy(info);
|
|
909
|
+
info.readonly = true;
|
|
910
|
+
}
|
|
911
|
+
if (anInstruction === 0 && topLevel && !options.schema.match(/\$_schema_/) && typeof info.add !== 'object') {
|
|
612
912
|
info.add = info.add ? ' ' + info.add + ' ' : '';
|
|
613
913
|
if (info.add.indexOf('ui-date') === -1 && !options.noautofocus && !info.containerType) {
|
|
614
914
|
info.add = info.add + 'autofocus ';
|
|
@@ -617,9 +917,10 @@ var fng;
|
|
|
617
917
|
var callHandleField = true;
|
|
618
918
|
if (info.directive) {
|
|
619
919
|
var directiveName = info.directive;
|
|
620
|
-
var newElement =
|
|
920
|
+
var newElement = info.customHeader || "";
|
|
921
|
+
newElement += '<' + directiveName + ' model="' + (options.model || 'record') + '"';
|
|
621
922
|
var thisElement = element[0];
|
|
622
|
-
inferMissingProperties(info);
|
|
923
|
+
inferMissingProperties(info, options);
|
|
623
924
|
for (var i = 0; i < thisElement.attributes.length; i++) {
|
|
624
925
|
var thisAttr = thisElement.attributes[i];
|
|
625
926
|
switch (thisAttr.nodeName) {
|
|
@@ -655,7 +956,9 @@ var fng;
|
|
|
655
956
|
break;
|
|
656
957
|
case 'object':
|
|
657
958
|
for (var subAdd in info.add) {
|
|
658
|
-
|
|
959
|
+
if (info.add.hasOwnProperty(subAdd)) {
|
|
960
|
+
newElement += ' ' + subAdd + '="' + info.add[subAdd].toString().replace(/"/g, '"') + '"';
|
|
961
|
+
}
|
|
659
962
|
}
|
|
660
963
|
break;
|
|
661
964
|
default:
|
|
@@ -664,13 +967,21 @@ var fng;
|
|
|
664
967
|
break;
|
|
665
968
|
case directiveCamel:
|
|
666
969
|
for (var subProp in info[prop]) {
|
|
667
|
-
|
|
970
|
+
if (info[prop].hasOwnProperty(subProp)) {
|
|
971
|
+
newElement += " ".concat(info.directive, "-").concat(subProp, "=\"");
|
|
972
|
+
if (typeof info[prop][subProp] === 'string') {
|
|
973
|
+
newElement += "".concat(info[prop][subProp].replace(/"/g, '"'), "\"");
|
|
974
|
+
}
|
|
975
|
+
else {
|
|
976
|
+
newElement += "".concat(JSON.stringify(info[prop][subProp]).replace(/"/g, '"'), "\"");
|
|
977
|
+
}
|
|
978
|
+
}
|
|
668
979
|
}
|
|
669
980
|
break;
|
|
670
981
|
default:
|
|
671
982
|
if (info[prop]) {
|
|
672
983
|
if (typeof info[prop] === 'string') {
|
|
673
|
-
newElement += ' fng-fld-' + prop + '="' + info[prop].
|
|
984
|
+
newElement += ' fng-fld-' + prop + '="' + info[prop].replace(/"/g, '"') + '"';
|
|
674
985
|
}
|
|
675
986
|
else {
|
|
676
987
|
newElement += ' fng-fld-' + prop + '="' + JSON.stringify(info[prop]).replace(/"/g, '"') + '"';
|
|
@@ -686,10 +997,13 @@ var fng;
|
|
|
686
997
|
}
|
|
687
998
|
}
|
|
688
999
|
newElement += 'ng-model="' + info.name + '"></' + directiveName + '>';
|
|
1000
|
+
newElement += (info.customFooter || "");
|
|
689
1001
|
result += newElement;
|
|
690
1002
|
callHandleField = false;
|
|
691
1003
|
}
|
|
692
1004
|
else if (info.containerType) {
|
|
1005
|
+
// for now, the following call will only consider security for tabs and not other container types.
|
|
1006
|
+
// hence why we...
|
|
693
1007
|
var parts = containerInstructions(info);
|
|
694
1008
|
switch (info.containerType) {
|
|
695
1009
|
case 'tab':
|
|
@@ -700,9 +1014,12 @@ var fng;
|
|
|
700
1014
|
var activeTabNo = _.findIndex(scope.tabs, function (tab) { return (tab.active); });
|
|
701
1015
|
scope.activeTabNo = activeTabNo >= 0 ? activeTabNo : 0;
|
|
702
1016
|
}
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
1017
|
+
// ...only check for this here!
|
|
1018
|
+
if (!parts.omit) {
|
|
1019
|
+
result += parts.before;
|
|
1020
|
+
result += processInstructions(info.content, null, options);
|
|
1021
|
+
result += parts.after;
|
|
1022
|
+
}
|
|
706
1023
|
break;
|
|
707
1024
|
case 'tabset':
|
|
708
1025
|
tabsSetup = tabsSetupState.Y;
|
|
@@ -722,9 +1039,7 @@ var fng;
|
|
|
722
1039
|
else if (options.subkey) {
|
|
723
1040
|
// Don't display fields that form part of the subkey, as they should not be edited (because in these circumstances they form some kind of key)
|
|
724
1041
|
var objectToSearch = angular.isArray(scope[options.subkey]) ? scope[options.subkey][0].keyList : scope[options.subkey].keyList;
|
|
725
|
-
if (_.find(objectToSearch, function (value, key) {
|
|
726
|
-
return scope[options.subkey].path + '.' + key === info.name;
|
|
727
|
-
})) {
|
|
1042
|
+
if (_.find(objectToSearch, function (value, key) { return scope[options.subkey].path + '.' + key === info.name; })) {
|
|
728
1043
|
callHandleField = false;
|
|
729
1044
|
}
|
|
730
1045
|
}
|
|
@@ -732,7 +1047,7 @@ var fng;
|
|
|
732
1047
|
// if (groupId) {
|
|
733
1048
|
// scope['showHide' + groupId] = true;
|
|
734
1049
|
// }
|
|
735
|
-
inferMissingProperties(info);
|
|
1050
|
+
inferMissingProperties(info, options);
|
|
736
1051
|
result += handleField(info, options);
|
|
737
1052
|
}
|
|
738
1053
|
}
|
|
@@ -746,8 +1061,9 @@ var fng;
|
|
|
746
1061
|
var unwatch = scope.$watch(attrs.schema, function (newValue) {
|
|
747
1062
|
if (newValue) {
|
|
748
1063
|
var newArrayValue = angular.isArray(newValue) ? newValue : [newValue]; // otherwise some old tests stop working for no real reason
|
|
749
|
-
if (newArrayValue.length > 0) {
|
|
1064
|
+
if (newArrayValue.length > 0 && typeof unwatch === "function") {
|
|
750
1065
|
unwatch();
|
|
1066
|
+
unwatch = null;
|
|
751
1067
|
var elementHtml = '';
|
|
752
1068
|
var recordAttribute = attrs.model || 'record'; // By default data comes from scope.record
|
|
753
1069
|
var theRecord = scope[recordAttribute];
|
|
@@ -761,12 +1077,13 @@ var fng;
|
|
|
761
1077
|
var customAttrs = '';
|
|
762
1078
|
for (var thisAttr in attrs) {
|
|
763
1079
|
if (attrs.hasOwnProperty(thisAttr)) {
|
|
764
|
-
if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model'].indexOf(thisAttr) === -1) {
|
|
1080
|
+
if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model', 'viewform'].indexOf(thisAttr) === -1) {
|
|
765
1081
|
customAttrs += ' ' + attrs.$attr[thisAttr] + '="' + attrs[thisAttr] + '"';
|
|
766
1082
|
}
|
|
767
1083
|
}
|
|
768
1084
|
}
|
|
769
|
-
|
|
1085
|
+
var tag = attrs.forceform ? 'ng-form' : 'form';
|
|
1086
|
+
elementHtml = "<".concat(tag, " name=\"").concat(scope.topLevelFormName, "\" class=\"").concat(convertFormStyleToClass(attrs.formstyle), "\" novalidate ").concat(customAttrs, ">");
|
|
770
1087
|
}
|
|
771
1088
|
if (theRecord === scope.topLevelFormName) {
|
|
772
1089
|
throw new Error('Model and Name must be distinct - they are both ' + theRecord);
|
|
@@ -782,10 +1099,12 @@ var fng;
|
|
|
782
1099
|
// If we have modelControllers we need to let them know when we have form + data
|
|
783
1100
|
var sharedData = scope[attrs.shared || 'sharedData'];
|
|
784
1101
|
var modelControllers_1 = sharedData ? sharedData.modelControllers : [];
|
|
785
|
-
if (subkeys.length > 0 || modelControllers_1.length > 0) {
|
|
1102
|
+
if ((subkeys.length > 0 || modelControllers_1.length > 0) && !scope.phaseWatcher) {
|
|
786
1103
|
var unwatch2 = scope.$watch('phase', function (newValue) {
|
|
787
|
-
|
|
1104
|
+
scope.phaseWatcher = true;
|
|
1105
|
+
if (newValue === 'ready' && typeof unwatch2 === "function") {
|
|
788
1106
|
unwatch2();
|
|
1107
|
+
unwatch2 = null;
|
|
789
1108
|
// Tell the 'model controllers' that the form and data are there
|
|
790
1109
|
for (var i = 0; i < modelControllers_1.length; i++) {
|
|
791
1110
|
if (modelControllers_1[i].onAllReady) {
|
|
@@ -814,7 +1133,7 @@ var fng;
|
|
|
814
1133
|
arrayOffset = scope[arrayToProcess[thisOffset].selectFunc](theRecord, info);
|
|
815
1134
|
}
|
|
816
1135
|
else if (arrayToProcess[thisOffset].keyList) {
|
|
817
|
-
// We are
|
|
1136
|
+
// We are choosing the array element by matching one or more keys
|
|
818
1137
|
var thisSubkeyList = arrayToProcess[thisOffset].keyList;
|
|
819
1138
|
for (arrayOffset = 0; arrayOffset < dataVal.length; arrayOffset++) {
|
|
820
1139
|
matching = true;
|
|
@@ -868,7 +1187,7 @@ var fng;
|
|
|
868
1187
|
}
|
|
869
1188
|
});
|
|
870
1189
|
}
|
|
871
|
-
$rootScope.$broadcast('formInputDone');
|
|
1190
|
+
$rootScope.$broadcast('formInputDone', attrs.name);
|
|
872
1191
|
if (formGenerator.updateDataDependentDisplay && theRecord && Object.keys(theRecord).length > 0) {
|
|
873
1192
|
// If this is not a test force the data dependent updates to the DOM
|
|
874
1193
|
formGenerator.updateDataDependentDisplay(theRecord, null, true, scope);
|
|
@@ -917,7 +1236,7 @@ var fng;
|
|
|
917
1236
|
/*@ngInject*/
|
|
918
1237
|
SearchCtrl.$inject = ["$scope", "$http", "$location", "routingService"];
|
|
919
1238
|
function SearchCtrl($scope, $http, $location, routingService) {
|
|
920
|
-
var
|
|
1239
|
+
var lastSearchSent;
|
|
921
1240
|
var _isNotMobile;
|
|
922
1241
|
_isNotMobile = (function () {
|
|
923
1242
|
var check = false;
|
|
@@ -957,7 +1276,7 @@ var fng;
|
|
|
957
1276
|
break;
|
|
958
1277
|
case 13:
|
|
959
1278
|
if ($scope.focus != null) {
|
|
960
|
-
$
|
|
1279
|
+
$location.url(makeUrlNoHtml5Hash($scope.results[$scope.focus]));
|
|
961
1280
|
}
|
|
962
1281
|
break;
|
|
963
1282
|
}
|
|
@@ -970,14 +1289,6 @@ var fng;
|
|
|
970
1289
|
$scope.results[index].focussed = true;
|
|
971
1290
|
$scope.focus = index;
|
|
972
1291
|
};
|
|
973
|
-
$scope.selectResult = function (resultNo) {
|
|
974
|
-
var result = $scope.results[resultNo];
|
|
975
|
-
var newURL = routingService.prefix() + '/' + result.resource + '/' + result.id + '/edit';
|
|
976
|
-
if (result.resourceTab) {
|
|
977
|
-
newURL += '/' + result.resourceTab;
|
|
978
|
-
}
|
|
979
|
-
$location.url(newURL);
|
|
980
|
-
};
|
|
981
1292
|
$scope.resultClass = function (index) {
|
|
982
1293
|
var resultClass = 'search-result';
|
|
983
1294
|
if ($scope.results && $scope.results[index].focussed) {
|
|
@@ -991,16 +1302,23 @@ var fng;
|
|
|
991
1302
|
$scope.results = [];
|
|
992
1303
|
$scope.focus = null;
|
|
993
1304
|
};
|
|
1305
|
+
function makeUrlNoHtml5Hash(result) {
|
|
1306
|
+
return result.url ? routingService.html5hash() + result.url.replace('|id|', result.id) :
|
|
1307
|
+
routingService.buildOperationUrl('edit', result.resource, undefined, result.id, result.resourceTab);
|
|
1308
|
+
}
|
|
994
1309
|
$scope.$watch('searchTarget', function (newValue) {
|
|
995
1310
|
if (newValue && newValue.length > 0) {
|
|
996
|
-
|
|
997
|
-
$http.get('/api/search?q=' + newValue).then(function (response) {
|
|
1311
|
+
lastSearchSent = $scope.testTime || new Date().valueOf();
|
|
1312
|
+
$http.get('/api/search?q=' + newValue + '&sentAt=' + lastSearchSent).then(function (response) {
|
|
998
1313
|
var data = response.data;
|
|
999
1314
|
// Check that we haven't fired off a subsequent request, in which
|
|
1000
1315
|
// case we are no longer interested in these results
|
|
1001
|
-
if (
|
|
1316
|
+
if (!data.timestamps || !data.timestamps.sentAt || Number.parseInt(data.timestamps.sentAt) === lastSearchSent) {
|
|
1002
1317
|
if ($scope.searchTarget.length > 0) {
|
|
1003
1318
|
$scope.results = data.results;
|
|
1319
|
+
$scope.results.forEach(function (result) {
|
|
1320
|
+
result.href = routingService.html5hash() + makeUrlNoHtml5Hash(result);
|
|
1321
|
+
});
|
|
1004
1322
|
$scope.moreCount = data.moreCount;
|
|
1005
1323
|
if (data.results.length > 0) {
|
|
1006
1324
|
$scope.errorClass = '';
|
|
@@ -1065,6 +1383,21 @@ var fng;
|
|
|
1065
1383
|
})(fng || (fng = {}));
|
|
1066
1384
|
/// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
|
|
1067
1385
|
var fng;
|
|
1386
|
+
(function (fng) {
|
|
1387
|
+
var filters;
|
|
1388
|
+
(function (filters) {
|
|
1389
|
+
/*@ngInject*/
|
|
1390
|
+
function extractTimestampFromMongoID() {
|
|
1391
|
+
return function (id) {
|
|
1392
|
+
var timestamp = id.substring(0, 8);
|
|
1393
|
+
return new Date(parseInt(timestamp, 16) * 1000);
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
filters.extractTimestampFromMongoID = extractTimestampFromMongoID;
|
|
1397
|
+
})(filters = fng.filters || (fng.filters = {}));
|
|
1398
|
+
})(fng || (fng = {}));
|
|
1399
|
+
/// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
|
|
1400
|
+
var fng;
|
|
1068
1401
|
(function (fng) {
|
|
1069
1402
|
var filters;
|
|
1070
1403
|
(function (filters) {
|
|
@@ -1245,6 +1578,7 @@ var fng;
|
|
|
1245
1578
|
controllerName += 'Ctrl';
|
|
1246
1579
|
locals.$scope = sharedData.modelControllers[level] = localScope;
|
|
1247
1580
|
var parentScope = localScope.$parent;
|
|
1581
|
+
parentScope.items = parentScope.items || [];
|
|
1248
1582
|
var addMenuOptions = function (array) {
|
|
1249
1583
|
angular.forEach(array, function (value) {
|
|
1250
1584
|
if (value.divider) {
|
|
@@ -1255,6 +1589,10 @@ var fng;
|
|
|
1255
1589
|
needDivider = false;
|
|
1256
1590
|
parentScope.items.push({ divider: true });
|
|
1257
1591
|
}
|
|
1592
|
+
if (!value.id) {
|
|
1593
|
+
// if it doesn't have an id, give it one, so every menu item is possible to secure
|
|
1594
|
+
value.id = _.camelCase(value.text || value.textFunc() || "");
|
|
1595
|
+
}
|
|
1258
1596
|
parentScope.items.push(value);
|
|
1259
1597
|
}
|
|
1260
1598
|
});
|
|
@@ -1276,6 +1614,11 @@ var fng;
|
|
|
1276
1614
|
locals.$scope.contextMenuPromise.then(function (array) { return addMenuOptions(array); });
|
|
1277
1615
|
}
|
|
1278
1616
|
}
|
|
1617
|
+
if (sharedData.modelNameDisplayPromise) {
|
|
1618
|
+
sharedData.modelNameDisplayPromise.then(function (value) {
|
|
1619
|
+
parentScope.modelNameDisplay = value;
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1279
1622
|
}
|
|
1280
1623
|
catch (error) {
|
|
1281
1624
|
// Check to see if error is no such controller - don't care
|
|
@@ -1305,7 +1648,7 @@ var fng;
|
|
|
1305
1648
|
routing: 'ngroute',
|
|
1306
1649
|
prefix: '' // How do we want to prefix our routes? If not empty string then first character must be slash (which is added if not)
|
|
1307
1650
|
};
|
|
1308
|
-
var postActions = ['edit'];
|
|
1651
|
+
var postActions = ['edit', 'view'];
|
|
1309
1652
|
var builtInRoutes = [
|
|
1310
1653
|
{
|
|
1311
1654
|
route: '/analyse/:model/:reportSchemaName',
|
|
@@ -1315,12 +1658,19 @@ var fng;
|
|
|
1315
1658
|
{ route: '/analyse/:model', state: 'analyse::model', templateUrl: 'base-analysis.html' },
|
|
1316
1659
|
{ route: '/:model/:id/edit', state: 'model::edit', templateUrl: 'base-edit.html' },
|
|
1317
1660
|
{ route: '/:model/:id/edit/:tab', state: 'model::edit::tab', templateUrl: 'base-edit.html' },
|
|
1661
|
+
{ route: '/:model/:id/view', state: 'model::edit', templateUrl: 'base-view.html' },
|
|
1662
|
+
{ route: '/:model/:id/view/:tab', state: 'model::view::tab', templateUrl: 'base-view.html' },
|
|
1318
1663
|
{ route: '/:model/new', state: 'model::new', templateUrl: 'base-edit.html' },
|
|
1319
1664
|
{ route: '/:model', state: 'model::list', templateUrl: 'base-list.html' },
|
|
1665
|
+
{ route: '/:model/viewonly', state: 'model::view', templateUrl: 'base-list-view.html' },
|
|
1666
|
+
// Non default form (subset of fields etc)
|
|
1320
1667
|
{ route: '/:model/:form/:id/edit', state: 'model::form::edit', templateUrl: 'base-edit.html' },
|
|
1321
1668
|
{ route: '/:model/:form/:id/edit/:tab', state: 'model::form::edit::tab', templateUrl: 'base-edit.html' },
|
|
1669
|
+
{ route: '/:model/:form/:id/view', state: 'model::form::view', templateUrl: 'base-view.html' },
|
|
1670
|
+
{ route: '/:model/:form/:id/view/:tab', state: 'model::form::view::tab', templateUrl: 'base-view.html' },
|
|
1322
1671
|
{ route: '/:model/:form/new', state: 'model::form::new', templateUrl: 'base-edit.html' },
|
|
1323
|
-
{ route: '/:model/:form', state: 'model::form::list', templateUrl: 'base-list.html' }
|
|
1672
|
+
{ route: '/:model/:form', state: 'model::form::list', templateUrl: 'base-list.html' },
|
|
1673
|
+
{ route: '/:model/:form/viewonly', state: 'model::form::list::view', templateUrl: 'base-list-view.html' } // list page with edit links to non default form
|
|
1324
1674
|
];
|
|
1325
1675
|
var _routeProvider, _stateProvider;
|
|
1326
1676
|
var lastRoute = null;
|
|
@@ -1374,6 +1724,12 @@ var fng;
|
|
|
1374
1724
|
case 'edit':
|
|
1375
1725
|
urlStr = modelString + formString + '/' + id + '/edit' + tabString;
|
|
1376
1726
|
break;
|
|
1727
|
+
case 'view':
|
|
1728
|
+
urlStr = modelString + formString + '/' + id + '/view' + tabString;
|
|
1729
|
+
break;
|
|
1730
|
+
case 'read':
|
|
1731
|
+
urlStr = modelString + formString + '/' + id + '/read' + tabString;
|
|
1732
|
+
break;
|
|
1377
1733
|
case 'new':
|
|
1378
1734
|
urlStr = modelString + formString + '/new' + tabString;
|
|
1379
1735
|
break;
|
|
@@ -1456,8 +1812,9 @@ var fng;
|
|
|
1456
1812
|
lastObject.modelName = locationSplit[1];
|
|
1457
1813
|
var lastParts_1 = [locationSplit[locationParts - 1], locationSplit[locationParts - 2]];
|
|
1458
1814
|
var newPos = lastParts_1.indexOf('new');
|
|
1815
|
+
var viewonlyPos = lastParts_1.indexOf('viewonly');
|
|
1459
1816
|
var actionPos = void 0;
|
|
1460
|
-
if (newPos === -1) {
|
|
1817
|
+
if (newPos === -1 && viewonlyPos === -1) {
|
|
1461
1818
|
actionPos = postActions.reduce(function (previousValue, currentValue) {
|
|
1462
1819
|
var pos = lastParts_1.indexOf(currentValue);
|
|
1463
1820
|
return pos > -1 ? pos : previousValue;
|
|
@@ -1467,10 +1824,13 @@ var fng;
|
|
|
1467
1824
|
lastObject.id = locationSplit[locationParts];
|
|
1468
1825
|
}
|
|
1469
1826
|
}
|
|
1470
|
-
else {
|
|
1827
|
+
else if (newPos !== -1) {
|
|
1471
1828
|
lastObject.newRecord = true;
|
|
1472
1829
|
locationParts -= (1 + newPos);
|
|
1473
1830
|
}
|
|
1831
|
+
else {
|
|
1832
|
+
locationParts -= (1 + viewonlyPos);
|
|
1833
|
+
}
|
|
1474
1834
|
if (actionPos === 1 || newPos === 1) {
|
|
1475
1835
|
lastObject.tab = lastParts_1[0];
|
|
1476
1836
|
}
|
|
@@ -1481,34 +1841,9 @@ var fng;
|
|
|
1481
1841
|
}
|
|
1482
1842
|
return lastObject;
|
|
1483
1843
|
};
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
// * Parser for the states provided by ui.router
|
|
1488
|
-
// */
|
|
1489
|
-
//'use strict';
|
|
1490
|
-
//formsAngular.factory('$stateParse', [function () {
|
|
1491
|
-
//
|
|
1492
|
-
// var lastObject = {};
|
|
1493
|
-
//
|
|
1494
|
-
// return function (state) {
|
|
1495
|
-
// if (state.current && state.current.name) {
|
|
1496
|
-
// lastObject = {newRecord: false};
|
|
1497
|
-
// lastObject.modelName = state.params.model;
|
|
1498
|
-
// if (state.current.name === 'model::list') {
|
|
1499
|
-
// lastObject = {index: true};
|
|
1500
|
-
// lastObject.modelName = state.params.model;
|
|
1501
|
-
// } else if (state.current.name === 'model::edit') {
|
|
1502
|
-
// lastObject.id = state.params.id;
|
|
1503
|
-
// } else if (state.current.name === 'model::new') {
|
|
1504
|
-
// lastObject.newRecord = true;
|
|
1505
|
-
// } else if (state.current.name === 'model::analyse') {
|
|
1506
|
-
// lastObject.analyse = true;
|
|
1507
|
-
// }
|
|
1508
|
-
// }
|
|
1509
|
-
// return lastObject;
|
|
1510
|
-
// };
|
|
1511
|
-
//}]);
|
|
1844
|
+
},
|
|
1845
|
+
html5hash: function () {
|
|
1846
|
+
return config.html5Mode ? '' : '#';
|
|
1512
1847
|
},
|
|
1513
1848
|
buildUrl: function (path) {
|
|
1514
1849
|
var url = config.html5Mode ? '' : '#';
|
|
@@ -1525,46 +1860,25 @@ var fng;
|
|
|
1525
1860
|
},
|
|
1526
1861
|
redirectTo: function () {
|
|
1527
1862
|
return function (operation, scope, location, id, tab) {
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
if (
|
|
1531
|
-
|
|
1863
|
+
location.search({}); // Lose any search parameters
|
|
1864
|
+
var urlStr;
|
|
1865
|
+
if (operation === 'onDelete') {
|
|
1866
|
+
if (config.onDelete) {
|
|
1867
|
+
if (config.onDelete === 'new') {
|
|
1868
|
+
urlStr = _buildOperationUrl(config.prefix, 'new', scope.modelName, scope.formName, id, tab);
|
|
1869
|
+
}
|
|
1870
|
+
else {
|
|
1871
|
+
urlStr = config.onDelete;
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
else {
|
|
1875
|
+
urlStr = _buildOperationUrl(config.prefix, 'list', scope.modelName, scope.formName, id, tab);
|
|
1876
|
+
}
|
|
1877
|
+
}
|
|
1878
|
+
else {
|
|
1879
|
+
urlStr = _buildOperationUrl(config.prefix, operation, scope.modelName, scope.formName, id, tab);
|
|
1532
1880
|
}
|
|
1533
|
-
var urlStr = _buildOperationUrl(config.prefix, operation, scope.modelName, scope.formName, id, tab);
|
|
1534
1881
|
location.path(urlStr);
|
|
1535
|
-
// break;
|
|
1536
|
-
// case 'uirouter' :
|
|
1537
|
-
// var formString = scope.formName ? ('/' + scope.formName) : '';
|
|
1538
|
-
// var modelString = config.prefix + '/' + scope.modelName;
|
|
1539
|
-
// console.log('form schemas not supported with ui-router');
|
|
1540
|
-
// switch (operation) {
|
|
1541
|
-
// case 'list' :
|
|
1542
|
-
// location.path(modelString + formString);
|
|
1543
|
-
// break;
|
|
1544
|
-
// case 'edit' :
|
|
1545
|
-
// location.path(modelString + formString + '/' + id + '/edit');
|
|
1546
|
-
// break;
|
|
1547
|
-
// case 'new' :
|
|
1548
|
-
// location.path(modelString + formString + '/new');
|
|
1549
|
-
// break;
|
|
1550
|
-
// }
|
|
1551
|
-
// switch (operation) {
|
|
1552
|
-
// case 'list' :
|
|
1553
|
-
// $state.go('model::list', { model: model });
|
|
1554
|
-
// break;
|
|
1555
|
-
// case 'edit' :
|
|
1556
|
-
// location.path('/' + scope.modelName + formString + '/' + id + '/edit');
|
|
1557
|
-
// break;
|
|
1558
|
-
// case 'new' :
|
|
1559
|
-
// location.path('/' + scope.modelName + formString + '/new');
|
|
1560
|
-
// break;
|
|
1561
|
-
// }
|
|
1562
|
-
// break;
|
|
1563
|
-
//
|
|
1564
|
-
//
|
|
1565
|
-
// // edit: $state.go('model::edit', {id: data._id, model: $scope.modelName });
|
|
1566
|
-
// // new: $state.go('model::new', {model: $scope.modelName});
|
|
1567
|
-
// break;
|
|
1568
1882
|
};
|
|
1569
1883
|
}
|
|
1570
1884
|
};
|
|
@@ -1586,7 +1900,7 @@ var fng;
|
|
|
1586
1900
|
* All methods should be state-less
|
|
1587
1901
|
*
|
|
1588
1902
|
*/
|
|
1589
|
-
function formGenerator($
|
|
1903
|
+
function formGenerator($filter, routingService, recordHandler, securityService) {
|
|
1590
1904
|
function handleSchema(description, source, destForm, destList, prefix, doRecursion, $scope, ctrlState) {
|
|
1591
1905
|
function handletabInfo(tabName, thisInst) {
|
|
1592
1906
|
var tabTitle = angular.copy(tabName);
|
|
@@ -1612,50 +1926,55 @@ var fng;
|
|
|
1612
1926
|
}
|
|
1613
1927
|
tab.content.push(thisInst);
|
|
1614
1928
|
}
|
|
1929
|
+
if (typeof $scope.onSchemaFetch === "function") {
|
|
1930
|
+
$scope.onSchemaFetch(description, source);
|
|
1931
|
+
}
|
|
1615
1932
|
for (var field in source) {
|
|
1616
|
-
if (field
|
|
1617
|
-
if (
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
}
|
|
1621
|
-
else if (source.hasOwnProperty(field)) {
|
|
1622
|
-
var mongooseType = source[field], mongooseOptions = mongooseType.options || {};
|
|
1623
|
-
var formData = mongooseOptions.form || {};
|
|
1624
|
-
if (mongooseType.schema && !formData.hidden) {
|
|
1625
|
-
if (doRecursion && destForm) {
|
|
1626
|
-
var schemaSchema = [];
|
|
1627
|
-
handleSchema('Nested ' + field, mongooseType.schema, schemaSchema, null, field + '.', true, $scope, ctrlState);
|
|
1628
|
-
var sectionInstructions = basicInstructions(field, formData, prefix);
|
|
1629
|
-
sectionInstructions.schema = schemaSchema;
|
|
1630
|
-
if (formData.tab) {
|
|
1631
|
-
handletabInfo(formData.tab, sectionInstructions);
|
|
1632
|
-
}
|
|
1633
|
-
if (formData.order !== undefined) {
|
|
1634
|
-
destForm.splice(formData.order, 0, sectionInstructions);
|
|
1635
|
-
}
|
|
1636
|
-
else {
|
|
1637
|
-
destForm.push(sectionInstructions);
|
|
1638
|
-
}
|
|
1933
|
+
if (source.hasOwnProperty(field)) {
|
|
1934
|
+
if (field === '_id') {
|
|
1935
|
+
if (destList && source['_id'].options && source['_id'].options.list) {
|
|
1936
|
+
handleListInfo(destList, source['_id'].options.list, field);
|
|
1639
1937
|
}
|
|
1640
1938
|
}
|
|
1641
|
-
else {
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1939
|
+
else if (source.hasOwnProperty(field)) {
|
|
1940
|
+
var mongooseType = source[field], mongooseOptions = mongooseType.options || {};
|
|
1941
|
+
var formData = mongooseOptions.form || {};
|
|
1942
|
+
if (mongooseType.schema && !formData.hidden) {
|
|
1943
|
+
if (doRecursion && destForm) {
|
|
1944
|
+
var schemaSchema = [];
|
|
1945
|
+
handleSchema('Nested ' + field, mongooseType.schema, schemaSchema, null, field + '.', true, $scope, ctrlState);
|
|
1946
|
+
var sectionInstructions = basicInstructions(field, formData, prefix);
|
|
1947
|
+
sectionInstructions.schema = schemaSchema;
|
|
1948
|
+
if (formData.tab) {
|
|
1949
|
+
handletabInfo(formData.tab, sectionInstructions);
|
|
1648
1950
|
}
|
|
1649
1951
|
if (formData.order !== undefined) {
|
|
1650
|
-
destForm.splice(formData.order, 0,
|
|
1952
|
+
destForm.splice(formData.order, 0, sectionInstructions);
|
|
1651
1953
|
}
|
|
1652
1954
|
else {
|
|
1653
|
-
destForm.push(
|
|
1955
|
+
destForm.push(sectionInstructions);
|
|
1654
1956
|
}
|
|
1655
1957
|
}
|
|
1656
1958
|
}
|
|
1657
|
-
|
|
1658
|
-
|
|
1959
|
+
else {
|
|
1960
|
+
if (destForm && !formData.hidden) {
|
|
1961
|
+
var formInstructions = basicInstructions(field, formData, prefix);
|
|
1962
|
+
if (handleConditionals(formInstructions.showIf, formInstructions.name, $scope) && field !== 'options') {
|
|
1963
|
+
var formInst = handleFieldType(formInstructions, mongooseType, mongooseOptions, $scope, ctrlState);
|
|
1964
|
+
if (formInst.tab) {
|
|
1965
|
+
handletabInfo(formInst.tab, formInst);
|
|
1966
|
+
}
|
|
1967
|
+
if (formData.order !== undefined) {
|
|
1968
|
+
destForm.splice(formData.order, 0, formInst);
|
|
1969
|
+
}
|
|
1970
|
+
else {
|
|
1971
|
+
destForm.push(formInst);
|
|
1972
|
+
}
|
|
1973
|
+
}
|
|
1974
|
+
}
|
|
1975
|
+
if (destList && mongooseOptions.list) {
|
|
1976
|
+
handleListInfo(destList, mongooseOptions.list, field);
|
|
1977
|
+
}
|
|
1659
1978
|
}
|
|
1660
1979
|
}
|
|
1661
1980
|
}
|
|
@@ -1679,6 +1998,9 @@ var fng;
|
|
|
1679
1998
|
// console.log($scope.tabs[0]['title'])
|
|
1680
1999
|
// $location.hash($scope.tabs[0]['title']);
|
|
1681
2000
|
// }
|
|
2001
|
+
if (typeof $scope.onSchemaProcessed === "function") {
|
|
2002
|
+
$scope.onSchemaProcessed(description, description.slice(0, 5) === 'Main ' ? $scope.baseSchema() : destForm);
|
|
2003
|
+
}
|
|
1682
2004
|
if (destList && destList.length === 0) {
|
|
1683
2005
|
handleEmptyList(description, destList, destForm, source);
|
|
1684
2006
|
}
|
|
@@ -1687,23 +2009,23 @@ var fng;
|
|
|
1687
2009
|
function performLookupSelect() {
|
|
1688
2010
|
formInstructions.options = recordHandler.suffixCleanId(formInstructions, 'Options');
|
|
1689
2011
|
formInstructions.ids = recordHandler.suffixCleanId(formInstructions, '_ids');
|
|
1690
|
-
if (!formInstructions.hidden
|
|
1691
|
-
if (
|
|
1692
|
-
mongooseOptions.ref
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
2012
|
+
if (!formInstructions.hidden) {
|
|
2013
|
+
if (mongooseOptions.ref) {
|
|
2014
|
+
recordHandler.setUpLookupOptions(mongooseOptions.ref, formInstructions, $scope, ctrlState, handleSchema);
|
|
2015
|
+
}
|
|
2016
|
+
else if (mongooseOptions.lookupListRef) {
|
|
2017
|
+
recordHandler.setUpLookupListOptions(mongooseOptions.lookupListRef, formInstructions, $scope, ctrlState);
|
|
2018
|
+
formInstructions.lookupListRef = mongooseOptions.lookupListRef;
|
|
2019
|
+
}
|
|
2020
|
+
else if (mongooseOptions.internalRef) {
|
|
2021
|
+
recordHandler.handleInternalLookup($scope, formInstructions, mongooseOptions.internalRef);
|
|
2022
|
+
formInstructions.internalRef = mongooseOptions.internalRef;
|
|
2023
|
+
}
|
|
2024
|
+
else if (mongooseOptions.customLookupOptions) {
|
|
2025
|
+
// nothing to do - call setUpCustomLookupOptions() when ready to provide id and option arrays
|
|
2026
|
+
}
|
|
2027
|
+
else {
|
|
2028
|
+
throw new Error("No supported select lookup type found in ".concat(formInstructions.name));
|
|
1707
2029
|
}
|
|
1708
2030
|
}
|
|
1709
2031
|
}
|
|
@@ -1715,7 +2037,7 @@ var fng;
|
|
|
1715
2037
|
angular.extend(formInstructions, mongooseType.options.form);
|
|
1716
2038
|
}
|
|
1717
2039
|
}
|
|
1718
|
-
if (mongooseType.instance === 'String') {
|
|
2040
|
+
if (mongooseType.instance === 'String' || (mongooseType.instance === 'ObjectID' && formInstructions.asText)) {
|
|
1719
2041
|
if (mongooseOptions.enum) {
|
|
1720
2042
|
formInstructions.type = formInstructions.type || 'select';
|
|
1721
2043
|
if (formInstructions.select2) {
|
|
@@ -1737,13 +2059,22 @@ var fng;
|
|
|
1737
2059
|
}
|
|
1738
2060
|
else if (mongooseType.instance === 'ObjectID') {
|
|
1739
2061
|
formInstructions.ref = mongooseOptions.ref;
|
|
1740
|
-
if (formInstructions.link
|
|
1741
|
-
formInstructions.
|
|
1742
|
-
|
|
2062
|
+
if (formInstructions.link) {
|
|
2063
|
+
if (formInstructions.link.linkOnly) {
|
|
2064
|
+
formInstructions.type = 'link';
|
|
2065
|
+
formInstructions.linktext = formInstructions.link.text;
|
|
2066
|
+
}
|
|
2067
|
+
else if (formInstructions.link.label) {
|
|
2068
|
+
formInstructions.linklabel = true;
|
|
2069
|
+
}
|
|
2070
|
+
else {
|
|
2071
|
+
console.log('Unsupported link setup');
|
|
2072
|
+
}
|
|
1743
2073
|
formInstructions.form = formInstructions.link.form;
|
|
2074
|
+
formInstructions.linktab = formInstructions.link.linktab;
|
|
1744
2075
|
delete formInstructions.link;
|
|
1745
2076
|
}
|
|
1746
|
-
|
|
2077
|
+
if (formInstructions.type !== 'link') {
|
|
1747
2078
|
formInstructions.type = 'select';
|
|
1748
2079
|
if (formInstructions.select2 || (mongooseOptions.form && mongooseOptions.form.select2)) {
|
|
1749
2080
|
console.log('support for fng-select2 has been removed in 0.8.3 - please convert to fng-ui-select');
|
|
@@ -1755,16 +2086,18 @@ var fng;
|
|
|
1755
2086
|
}
|
|
1756
2087
|
else if (mongooseType.instance === 'Date') {
|
|
1757
2088
|
if (!formInstructions.type) {
|
|
2089
|
+
formInstructions.intType = 'date';
|
|
1758
2090
|
if (formInstructions.readonly) {
|
|
1759
2091
|
formInstructions.type = 'text';
|
|
1760
2092
|
}
|
|
1761
2093
|
else if (formInstructions.directive) {
|
|
1762
|
-
formInstructions.type = 'text';
|
|
2094
|
+
formInstructions.type = 'text';
|
|
1763
2095
|
}
|
|
1764
2096
|
else {
|
|
1765
2097
|
try {
|
|
1766
2098
|
formInstructions.add = formInstructions.add || '';
|
|
1767
|
-
|
|
2099
|
+
// Check whether DatePicker is installed
|
|
2100
|
+
angular.module('ui.date').requires;
|
|
1768
2101
|
formInstructions.type = 'text';
|
|
1769
2102
|
formInstructions.add += ' ui-date ui-date-format ';
|
|
1770
2103
|
// formInstructions.add += ' ui-date ui-date-format datepicker-popup-fix ';
|
|
@@ -1777,10 +2110,10 @@ var fng;
|
|
|
1777
2110
|
}
|
|
1778
2111
|
}
|
|
1779
2112
|
else if (mongooseType.instance.toLowerCase() === 'boolean') {
|
|
1780
|
-
formInstructions.type = 'checkbox';
|
|
2113
|
+
formInstructions.type = formInstructions.type || 'checkbox';
|
|
1781
2114
|
}
|
|
1782
2115
|
else if (mongooseType.instance === 'Number') {
|
|
1783
|
-
formInstructions.type = 'number';
|
|
2116
|
+
formInstructions.type = formInstructions.type || 'number';
|
|
1784
2117
|
if (mongooseOptions.min !== undefined) {
|
|
1785
2118
|
formInstructions.add = 'min="' + mongooseOptions.min + '" ' + (formInstructions.add || '');
|
|
1786
2119
|
}
|
|
@@ -1800,11 +2133,17 @@ var fng;
|
|
|
1800
2133
|
if (mongooseOptions.readonly) {
|
|
1801
2134
|
formInstructions['readonly'] = true;
|
|
1802
2135
|
}
|
|
2136
|
+
if (mongooseType.defaultValue !== undefined) {
|
|
2137
|
+
formInstructions.defaultValue = mongooseType.defaultValue;
|
|
2138
|
+
}
|
|
2139
|
+
else if (mongooseType.options && mongooseType.options.default !== undefined) {
|
|
2140
|
+
console.log('No support for default with no value, yet');
|
|
2141
|
+
}
|
|
1803
2142
|
return formInstructions;
|
|
1804
2143
|
}
|
|
1805
|
-
function getArrayFieldToExtend(fieldName, $scope) {
|
|
2144
|
+
function getArrayFieldToExtend(fieldName, $scope, modelOverride) {
|
|
1806
2145
|
var fieldParts = fieldName.split('.');
|
|
1807
|
-
var arrayField = $scope.record;
|
|
2146
|
+
var arrayField = modelOverride || $scope.record;
|
|
1808
2147
|
for (var i = 0, l = fieldParts.length; i < l; i++) {
|
|
1809
2148
|
if (!arrayField[fieldParts[i]]) {
|
|
1810
2149
|
if (i === l - 1) {
|
|
@@ -1921,6 +2260,9 @@ var fng;
|
|
|
1921
2260
|
generateEditUrl: function generateEditUrl(obj, $scope) {
|
|
1922
2261
|
return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/edit');
|
|
1923
2262
|
},
|
|
2263
|
+
generateViewUrl: function generateViewUrl(obj, $scope) {
|
|
2264
|
+
return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/view');
|
|
2265
|
+
},
|
|
1924
2266
|
generateNewUrl: function generateNewUrl($scope) {
|
|
1925
2267
|
return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + 'new');
|
|
1926
2268
|
},
|
|
@@ -2005,25 +2347,55 @@ var fng;
|
|
|
2005
2347
|
}
|
|
2006
2348
|
return forceNextTime;
|
|
2007
2349
|
},
|
|
2008
|
-
add: function add(fieldName, $event, $scope) {
|
|
2009
|
-
var
|
|
2010
|
-
|
|
2011
|
-
|
|
2350
|
+
add: function add(fieldName, $event, $scope, modelOverride) {
|
|
2351
|
+
var _a, _b;
|
|
2352
|
+
// for buttons, the click event won't fire if the disabled attribute exists, but the same is not true of
|
|
2353
|
+
// icons, so we need to check this for simple array item addition
|
|
2354
|
+
if (((_a = $event === null || $event === void 0 ? void 0 : $event.target) === null || _a === void 0 ? void 0 : _a.hasAttribute) && $event.target.hasAttribute("disabled")) {
|
|
2355
|
+
return $event.preventDefault();
|
|
2356
|
+
}
|
|
2357
|
+
// check that target element is visible. May not be reliable - see https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
|
|
2358
|
+
if ($event.target.offsetParent) {
|
|
2359
|
+
var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
|
|
2360
|
+
var schemaElement = $scope.formSchema.find(function (f) { return f.name === fieldName; }); // In case someone is using the formSchema directly
|
|
2361
|
+
var subSchema = schemaElement ? schemaElement.schema : null;
|
|
2362
|
+
var obj = subSchema ? $scope.setDefaults(subSchema, fieldName + '.') : {};
|
|
2363
|
+
if (typeof ((_b = $scope.dataEventFunctions) === null || _b === void 0 ? void 0 : _b.onInitialiseNewSubDoc) === "function") {
|
|
2364
|
+
$scope.dataEventFunctions.onInitialiseNewSubDoc(fieldName, subSchema, obj);
|
|
2365
|
+
}
|
|
2366
|
+
arrayField.push(obj);
|
|
2367
|
+
$scope.setFormDirty($event);
|
|
2368
|
+
}
|
|
2012
2369
|
},
|
|
2013
|
-
unshift: function unshift(fieldName, $event, $scope) {
|
|
2014
|
-
var arrayField = getArrayFieldToExtend(fieldName, $scope);
|
|
2370
|
+
unshift: function unshift(fieldName, $event, $scope, modelOverride) {
|
|
2371
|
+
var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
|
|
2015
2372
|
arrayField.unshift({});
|
|
2016
2373
|
$scope.setFormDirty($event);
|
|
2017
2374
|
},
|
|
2018
|
-
remove: function remove(fieldName, value, $event, $scope) {
|
|
2375
|
+
remove: function remove(fieldName, value, $event, $scope, modelOverride) {
|
|
2376
|
+
var _a;
|
|
2377
|
+
// for buttons, the click event won't fire if the disabled attribute exists, but the same is not true of
|
|
2378
|
+
// icons, so we need to check this for simple array item removal
|
|
2379
|
+
if (((_a = $event === null || $event === void 0 ? void 0 : $event.target) === null || _a === void 0 ? void 0 : _a.hasAttribute) && $event.target.hasAttribute("disabled")) {
|
|
2380
|
+
return $event.preventDefault();
|
|
2381
|
+
}
|
|
2019
2382
|
// Remove an element from an array
|
|
2020
|
-
var
|
|
2021
|
-
var
|
|
2022
|
-
|
|
2023
|
-
|
|
2383
|
+
var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
|
|
2384
|
+
var err;
|
|
2385
|
+
if (typeof $scope.dataEventFunctions.onDeleteSubDoc === "function") {
|
|
2386
|
+
var schemaElement = $scope.formSchema.find(function (f) {
|
|
2387
|
+
return f.name === fieldName;
|
|
2388
|
+
});
|
|
2389
|
+
var subSchema = schemaElement ? schemaElement.schema : null;
|
|
2390
|
+
err = $scope.dataEventFunctions.onDeleteSubDoc(fieldName, subSchema, arrayField, value);
|
|
2391
|
+
}
|
|
2392
|
+
if (err) {
|
|
2393
|
+
$scope.showError(err);
|
|
2394
|
+
}
|
|
2395
|
+
else {
|
|
2396
|
+
arrayField.splice(value, 1);
|
|
2397
|
+
$scope.setFormDirty($event);
|
|
2024
2398
|
}
|
|
2025
|
-
arrayField.splice(value, 1);
|
|
2026
|
-
$scope.setFormDirty($event);
|
|
2027
2399
|
},
|
|
2028
2400
|
hasError: function hasError(formName, name, index, $scope) {
|
|
2029
2401
|
var result = false;
|
|
@@ -2035,7 +2407,7 @@ var fng;
|
|
|
2035
2407
|
// Cannot assume that directives will use the same methods
|
|
2036
2408
|
if (form) {
|
|
2037
2409
|
var field_1 = form[name];
|
|
2038
|
-
if (field_1 && field_1.$invalid) {
|
|
2410
|
+
if (field_1 && field_1.$invalid && !field_1.$$attr.readonly) {
|
|
2039
2411
|
if (field_1.$dirty) {
|
|
2040
2412
|
result = true;
|
|
2041
2413
|
}
|
|
@@ -2055,7 +2427,7 @@ var fng;
|
|
|
2055
2427
|
}
|
|
2056
2428
|
return result;
|
|
2057
2429
|
},
|
|
2058
|
-
decorateScope: function decorateScope($scope, formGeneratorInstance, recordHandlerInstance, sharedData) {
|
|
2430
|
+
decorateScope: function decorateScope($scope, formGeneratorInstance, recordHandlerInstance, sharedData, pseudoUrl) {
|
|
2059
2431
|
$scope.record = sharedData.record;
|
|
2060
2432
|
$scope.phase = 'init';
|
|
2061
2433
|
$scope.disableFunctions = sharedData.disableFunctions;
|
|
@@ -2072,17 +2444,34 @@ var fng;
|
|
|
2072
2444
|
$scope.pageSize = 60;
|
|
2073
2445
|
$scope.pagesLoaded = 0;
|
|
2074
2446
|
sharedData.baseScope = $scope;
|
|
2447
|
+
securityService.decorateSecurableScope($scope, { pseudoUrl: pseudoUrl });
|
|
2075
2448
|
$scope.generateEditUrl = function (obj) {
|
|
2076
2449
|
return formGeneratorInstance.generateEditUrl(obj, $scope);
|
|
2077
2450
|
};
|
|
2451
|
+
$scope.generateViewUrl = function (obj) {
|
|
2452
|
+
return formGeneratorInstance.generateViewUrl(obj, $scope);
|
|
2453
|
+
};
|
|
2078
2454
|
$scope.generateNewUrl = function () {
|
|
2079
2455
|
return formGeneratorInstance.generateNewUrl($scope);
|
|
2080
2456
|
};
|
|
2081
2457
|
$scope.scrollTheList = function () {
|
|
2082
|
-
|
|
2458
|
+
var _a;
|
|
2459
|
+
// wait until we have the list schema. until we get a non-empty listSchema (which might never
|
|
2460
|
+
// happen if we don't have permission to GET it), then there's no point requesting the data
|
|
2461
|
+
if (((_a = $scope.listSchema) === null || _a === void 0 ? void 0 : _a.length) > 0) {
|
|
2462
|
+
return recordHandlerInstance.scrollTheList($scope);
|
|
2463
|
+
}
|
|
2464
|
+
else {
|
|
2465
|
+
var unwatch_1 = $scope.$watchCollection("listSchema", function (newValue) {
|
|
2466
|
+
if ((newValue === null || newValue === void 0 ? void 0 : newValue.length) > 0) {
|
|
2467
|
+
unwatch_1();
|
|
2468
|
+
return recordHandlerInstance.scrollTheList($scope);
|
|
2469
|
+
}
|
|
2470
|
+
});
|
|
2471
|
+
}
|
|
2083
2472
|
};
|
|
2084
2473
|
$scope.getListData = function (record, fieldName) {
|
|
2085
|
-
return recordHandlerInstance.getListData(
|
|
2474
|
+
return recordHandlerInstance.getListData(record, fieldName, $scope.listSchema, $scope);
|
|
2086
2475
|
};
|
|
2087
2476
|
$scope.setPristine = function (clearErrors) {
|
|
2088
2477
|
if (clearErrors) {
|
|
@@ -2104,17 +2493,17 @@ var fng;
|
|
|
2104
2493
|
console.log('setFormDirty called without an event (fine in a unit test)');
|
|
2105
2494
|
}
|
|
2106
2495
|
};
|
|
2107
|
-
$scope.add = function (fieldName, $event) {
|
|
2108
|
-
return formGeneratorInstance.add(fieldName, $event, $scope);
|
|
2496
|
+
$scope.add = function (fieldName, $event, modelOverride) {
|
|
2497
|
+
return formGeneratorInstance.add(fieldName, $event, $scope, modelOverride);
|
|
2109
2498
|
};
|
|
2110
2499
|
$scope.hasError = function (form, name, index) {
|
|
2111
2500
|
return formGeneratorInstance.hasError(form, name, index, $scope);
|
|
2112
2501
|
};
|
|
2113
|
-
$scope.unshift = function (fieldName, $event) {
|
|
2114
|
-
return formGeneratorInstance.unshift(fieldName, $event, $scope);
|
|
2502
|
+
$scope.unshift = function (fieldName, $event, modelOverride) {
|
|
2503
|
+
return formGeneratorInstance.unshift(fieldName, $event, $scope, modelOverride);
|
|
2115
2504
|
};
|
|
2116
|
-
$scope.remove = function (fieldName, value, $event) {
|
|
2117
|
-
return formGeneratorInstance.remove(fieldName, value, $event, $scope);
|
|
2505
|
+
$scope.remove = function (fieldName, value, $event, modelOverride) {
|
|
2506
|
+
return formGeneratorInstance.remove(fieldName, value, $event, $scope, modelOverride);
|
|
2118
2507
|
};
|
|
2119
2508
|
$scope.baseSchema = function () {
|
|
2120
2509
|
return ($scope.tabs.length ? $scope.tabs : $scope.formSchema);
|
|
@@ -2129,7 +2518,7 @@ var fng;
|
|
|
2129
2518
|
};
|
|
2130
2519
|
}
|
|
2131
2520
|
services.formGenerator = formGenerator;
|
|
2132
|
-
formGenerator.$inject = ["$
|
|
2521
|
+
formGenerator.$inject = ["$filter", "routingService", "recordHandler", "securityService"];
|
|
2133
2522
|
})(services = fng.services || (fng.services = {}));
|
|
2134
2523
|
})(fng || (fng = {}));
|
|
2135
2524
|
/// <reference path="../../index.d.ts" />
|
|
@@ -2138,8 +2527,8 @@ var fng;
|
|
|
2138
2527
|
var services;
|
|
2139
2528
|
(function (services) {
|
|
2140
2529
|
/*@ngInject*/
|
|
2141
|
-
formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService"];
|
|
2142
|
-
function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService) {
|
|
2530
|
+
formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "securityService", "$filter"];
|
|
2531
|
+
function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, securityService, $filter) {
|
|
2143
2532
|
function generateNgShow(showWhen, model) {
|
|
2144
2533
|
function evaluateSide(side) {
|
|
2145
2534
|
var result = side;
|
|
@@ -2167,29 +2556,190 @@ var fng;
|
|
|
2167
2556
|
}
|
|
2168
2557
|
return evaluateSide(showWhen.lhs) + conditionSymbols[conditionPos] + evaluateSide(showWhen.rhs);
|
|
2169
2558
|
}
|
|
2170
|
-
var isHorizontalStyle = function isHorizontalStyle(formStyle) {
|
|
2171
|
-
|
|
2559
|
+
var isHorizontalStyle = function isHorizontalStyle(formStyle, includeStacked) {
|
|
2560
|
+
var exclude = ['vertical', 'inline'];
|
|
2561
|
+
if (!includeStacked) {
|
|
2562
|
+
exclude.push('stacked');
|
|
2563
|
+
}
|
|
2564
|
+
return (!formStyle || formStyle === 'undefined' || !exclude.includes(formStyle));
|
|
2172
2565
|
};
|
|
2173
2566
|
function glyphClass() {
|
|
2174
|
-
return (cssFrameworkService.framework() === 'bs2'
|
|
2567
|
+
return (cssFrameworkService.framework() === 'bs2' ? 'icon' : 'glyphicon glyphicon');
|
|
2568
|
+
}
|
|
2569
|
+
// Generate two strings:
|
|
2570
|
+
// 1. firstly, attribute(s) that could be added to element(s) representing the field with the given
|
|
2571
|
+
// parameters to enable or disable it according to the prevailing security rules.
|
|
2572
|
+
// 2. secondly, attribute(s) that could be added to a element - regardless of whether or not it will
|
|
2573
|
+
// actually be disabled on this occasion - to identify it as being potentially disableable
|
|
2574
|
+
// This function is a more complicated version of securityService.generateDisabledAttr, also taking into
|
|
2575
|
+
// account the fact that fieldInfo.readonly can influence the disabled state of a field.
|
|
2576
|
+
// nonUniqueId should be required only in cases where a sub-sub schema has been defined in a directive
|
|
2577
|
+
// as a means of getting around the single-level-of-nesting limitation. in that case, where the
|
|
2578
|
+
// directive's template then includes a <form-input> tag, it is likely that the ids of the sub-sub-schema
|
|
2579
|
+
// elements will include $index from a parent scope (responsible for the sub-schema) in order to
|
|
2580
|
+
// ensure its uniqueness, and in this case (as we are not explicitely managing the addition of the
|
|
2581
|
+
// {{ $index }} expr), we need to be given a version of the id that does not include that expression.
|
|
2582
|
+
// where nonUniqueId is provided, we will also use this for determining ancestors, because in the
|
|
2583
|
+
// nested sub-schema scenario described above, the names of the fields in the sub-sub-schema will
|
|
2584
|
+
// probably not identify the full ancestry.
|
|
2585
|
+
function handleReadOnlyDisabled(partialFieldInfo, scope) {
|
|
2586
|
+
var id = partialFieldInfo.nonUniqueId || partialFieldInfo.id;
|
|
2587
|
+
function getActuallyDisabledAttr() {
|
|
2588
|
+
if (partialFieldInfo.readonly && typeof partialFieldInfo.readonly === "boolean") {
|
|
2589
|
+
// if we have a true-valued readonly property then this trumps whatever security rule might apply to this field
|
|
2590
|
+
return " disabled ";
|
|
2591
|
+
}
|
|
2592
|
+
function wrapReadOnly() {
|
|
2593
|
+
return partialFieldInfo.readonly ? " ng-disabled=\"".concat(partialFieldInfo.readonly, "\" ") : "";
|
|
2594
|
+
}
|
|
2595
|
+
if (!id || !securityService.canDoSecurityNow(scope, "disabled")) {
|
|
2596
|
+
// no security, so we're just concerned about what value fieldInfo.readonly has
|
|
2597
|
+
return wrapReadOnly();
|
|
2598
|
+
}
|
|
2599
|
+
// if scope has been decorated with a requiresDisabledChildren function, we will be using that to check whether any
|
|
2600
|
+
// of the ancestors of this field's element require their children to be disabled. if they do, that means us!
|
|
2601
|
+
var ancestorIds = [];
|
|
2602
|
+
if (!!scope.requiresDisabledChildren) {
|
|
2603
|
+
var ancestors = void 0;
|
|
2604
|
+
// if we have been provided with a nonUniqueId, we should use that to determine ancestors, because in this case,
|
|
2605
|
+
// the name will not be reliable
|
|
2606
|
+
if (partialFieldInfo.nonUniqueId) {
|
|
2607
|
+
var ancestorStr = partialFieldInfo.nonUniqueId.startsWith("f_") ? partialFieldInfo.nonUniqueId.substring(2) : partialFieldInfo.nonUniqueId;
|
|
2608
|
+
ancestors = ancestorStr.split("_");
|
|
2609
|
+
}
|
|
2610
|
+
else {
|
|
2611
|
+
ancestors = partialFieldInfo.name.split(".");
|
|
2612
|
+
}
|
|
2613
|
+
ancestors.pop();
|
|
2614
|
+
while (ancestors.length > 0) {
|
|
2615
|
+
ancestorIds.push("f_".concat(ancestors.join("_")));
|
|
2616
|
+
ancestors.pop();
|
|
2617
|
+
}
|
|
2618
|
+
}
|
|
2619
|
+
if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
|
|
2620
|
+
// "instant" security is evaluated now, and a positive result trumps whatever fieldInfo.readonly might be set to
|
|
2621
|
+
if (scope.isSecurelyDisabled(id)) {
|
|
2622
|
+
return " disabled ";
|
|
2623
|
+
}
|
|
2624
|
+
else {
|
|
2625
|
+
for (var _i = 0, ancestorIds_1 = ancestorIds; _i < ancestorIds_1.length; _i++) {
|
|
2626
|
+
var ancestorId = ancestorIds_1[_i];
|
|
2627
|
+
if (scope.requiresDisabledChildren(ancestorId)) {
|
|
2628
|
+
return " disabled ";
|
|
2629
|
+
}
|
|
2630
|
+
}
|
|
2631
|
+
return wrapReadOnly();
|
|
2632
|
+
}
|
|
2633
|
+
}
|
|
2634
|
+
var securityFuncStr = "isSecurelyDisabled('".concat(id, "')");
|
|
2635
|
+
if (ancestorIds.length > 0) {
|
|
2636
|
+
var ancestorStr = ancestorIds.map(function (aid) { return "requiresDisabledChildren('".concat(aid, "')"); });
|
|
2637
|
+
securityFuncStr = "(".concat(securityFuncStr, " || ").concat(ancestorStr.join(" || "), ")");
|
|
2638
|
+
}
|
|
2639
|
+
var oneTimeBinding = fng.formsAngular.elemSecurityFuncBinding === "one-time";
|
|
2640
|
+
if (partialFieldInfo.readonly) {
|
|
2641
|
+
// we have both security and a read-only attribute to deal with
|
|
2642
|
+
if (oneTimeBinding) {
|
|
2643
|
+
// if our field has a string-typed readonly attribute *and* one-time binding is required by our securityFunc, we
|
|
2644
|
+
// cannot simply combine these into a single ng-disabled expression, because the readonly property is highly
|
|
2645
|
+
// likely to be model-dependent and therefore cannot use one-time-binding. the best we can do in this case is
|
|
2646
|
+
// to use ng-disabled for the field's readonly property, and a one-time-bound ng-readonly for the securityFunc.
|
|
2647
|
+
// this is not perfect, because in the case of selects, ng-readonly doesn't actually prevent the user from
|
|
2648
|
+
// making a selection. however, the select will be styled as if it is disabled (including the not-allowed
|
|
2649
|
+
// cursor), which should deter the user in most cases.
|
|
2650
|
+
return wrapReadOnly() + "ng-readonly=\"::".concat(securityFuncStr, "\" ");
|
|
2651
|
+
}
|
|
2652
|
+
else {
|
|
2653
|
+
// if we have both things and we are *NOT* required to use one-time binding for the securityFunc, then they can
|
|
2654
|
+
// be combined into a single ng-disabled expression
|
|
2655
|
+
return " ng-disabled=\"".concat(securityFuncStr, " || ").concat(partialFieldInfo.readonly, "\" ");
|
|
2656
|
+
}
|
|
2657
|
+
}
|
|
2658
|
+
else {
|
|
2659
|
+
// we have security only
|
|
2660
|
+
return " ng-disabled=\"".concat(oneTimeBinding ? "::" : "").concat(securityFuncStr, "\" ");
|
|
2661
|
+
}
|
|
2662
|
+
}
|
|
2663
|
+
return [getActuallyDisabledAttr(), securityService.getDisableableAttrs(id)];
|
|
2664
|
+
}
|
|
2665
|
+
function generateArrayElementIdString(idString, info, options) {
|
|
2666
|
+
if (options.subschema && options.model) {
|
|
2667
|
+
// for subschemas, it is possible that our model will begin with $parent., or $parent.$parent. (etc). though a bit of
|
|
2668
|
+
// a hack where this does occur (probably where a directive used by a sub-schema is using a nested <form-input>
|
|
2669
|
+
// directive), we need to look for the $index in the same place as our model is looking for data.
|
|
2670
|
+
var model = options.model;
|
|
2671
|
+
var nestedSteps = 0;
|
|
2672
|
+
var stepIndicator = "$parent.";
|
|
2673
|
+
while (model.startsWith(stepIndicator)) {
|
|
2674
|
+
nestedSteps++;
|
|
2675
|
+
model = model.substring(stepIndicator.length);
|
|
2676
|
+
}
|
|
2677
|
+
return "".concat(idString, "_{{").concat(stepIndicator.repeat(nestedSteps), "$index}}");
|
|
2678
|
+
}
|
|
2679
|
+
else {
|
|
2680
|
+
return "".concat(idString, "_{{$index}}");
|
|
2681
|
+
}
|
|
2682
|
+
}
|
|
2683
|
+
function genDisableableAncestorStr(id) {
|
|
2684
|
+
return securityService.getDisableableAncestorAttrs(id);
|
|
2685
|
+
}
|
|
2686
|
+
function isArrayElement(scope, info, options) {
|
|
2687
|
+
return scope["$index"] !== undefined || !!options.subschema;
|
|
2175
2688
|
}
|
|
2176
2689
|
return {
|
|
2177
2690
|
isHorizontalStyle: isHorizontalStyle,
|
|
2691
|
+
isArrayElement: isArrayElement,
|
|
2178
2692
|
fieldChrome: function fieldChrome(scope, info, options) {
|
|
2693
|
+
var insert = '';
|
|
2694
|
+
if (info.id && typeof info.id.replace === "function") {
|
|
2695
|
+
var uniqueIdStr = info.nonUniqueId || info.nonuniqueid || info.id;
|
|
2696
|
+
var idStr = void 0;
|
|
2697
|
+
// replace any . that appear in info.id with "-", but not those that appear between {{ and }}
|
|
2698
|
+
if (info.id.includes(".") && info.id.includes("{{")) {
|
|
2699
|
+
idStr = "cg_";
|
|
2700
|
+
var inExpr = false;
|
|
2701
|
+
for (var i = 0; i < info.id.length; i++) {
|
|
2702
|
+
if (info.id[i] === "{" && info.id[i - 1] === "{") {
|
|
2703
|
+
inExpr = true;
|
|
2704
|
+
}
|
|
2705
|
+
else if (info.id[i] === "}" && info.id[i - 1] === "}") {
|
|
2706
|
+
inExpr = false;
|
|
2707
|
+
}
|
|
2708
|
+
if (inExpr || info.id[i] !== ".") {
|
|
2709
|
+
idStr += info.id[i];
|
|
2710
|
+
}
|
|
2711
|
+
else {
|
|
2712
|
+
idStr += "-";
|
|
2713
|
+
}
|
|
2714
|
+
}
|
|
2715
|
+
}
|
|
2716
|
+
else {
|
|
2717
|
+
idStr = "cg_".concat(info.id.replace(/\./g, '-'));
|
|
2718
|
+
}
|
|
2719
|
+
uniqueIdStr = "cg_".concat(uniqueIdStr.replace(/\./g, '-'));
|
|
2720
|
+
var visibility = securityService.considerVisibility(uniqueIdStr, scope);
|
|
2721
|
+
if (visibility.omit) {
|
|
2722
|
+
// we already know this field should be invisible, so we needn't add anything for it
|
|
2723
|
+
return { omit: true };
|
|
2724
|
+
}
|
|
2725
|
+
insert += "id=\"".concat(isArrayElement(scope, info, options) ? generateArrayElementIdString(idStr, info, options) : idStr, "\"");
|
|
2726
|
+
if (visibility.visibilityAttr) {
|
|
2727
|
+
// an angular expression to determine the visibility of this field later...
|
|
2728
|
+
insert += " ".concat(visibility.visibilityAttr);
|
|
2729
|
+
}
|
|
2730
|
+
}
|
|
2179
2731
|
var classes = info.classes || '';
|
|
2180
2732
|
var template = '';
|
|
2181
2733
|
var closeTag = '';
|
|
2182
|
-
var insert = '';
|
|
2183
2734
|
info.showWhen = info.showWhen || info.showwhen; // deal with use within a directive
|
|
2184
2735
|
if (info.showWhen) {
|
|
2185
2736
|
if (typeof info.showWhen === 'string') {
|
|
2186
|
-
insert += 'ng-show="' + info.showWhen + '"';
|
|
2737
|
+
insert += ' ng-show="' + info.showWhen + '"';
|
|
2187
2738
|
}
|
|
2188
2739
|
else {
|
|
2189
|
-
insert += 'ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
|
|
2740
|
+
insert += ' ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
|
|
2190
2741
|
}
|
|
2191
2742
|
}
|
|
2192
|
-
insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
|
|
2193
2743
|
if (cssFrameworkService.framework() === 'bs3') {
|
|
2194
2744
|
classes += ' form-group';
|
|
2195
2745
|
if (options.formstyle === 'vertical' && info.size !== 'block-level') {
|
|
@@ -2214,7 +2764,7 @@ var fng;
|
|
|
2214
2764
|
closeTag += '</div>';
|
|
2215
2765
|
}
|
|
2216
2766
|
else {
|
|
2217
|
-
if (isHorizontalStyle(options.formstyle)) {
|
|
2767
|
+
if (isHorizontalStyle(options.formstyle, true)) {
|
|
2218
2768
|
template += '<div' + addAllService.addAll(scope, 'Group', 'control-group', options);
|
|
2219
2769
|
closeTag = '</div>';
|
|
2220
2770
|
}
|
|
@@ -2228,11 +2778,13 @@ var fng;
|
|
|
2228
2778
|
},
|
|
2229
2779
|
label: function label(scope, fieldInfo, addButtonMarkup, options) {
|
|
2230
2780
|
var labelHTML = '';
|
|
2231
|
-
if ((cssFrameworkService.framework() === 'bs3' || (options.formstyle
|
|
2781
|
+
if ((cssFrameworkService.framework() === 'bs3' || (!['inline', 'stacked'].includes(options.formstyle) && fieldInfo.label !== '')) || addButtonMarkup) {
|
|
2232
2782
|
labelHTML = '<label';
|
|
2233
2783
|
var classes = 'control-label';
|
|
2234
|
-
if (isHorizontalStyle(options.formstyle)) {
|
|
2235
|
-
|
|
2784
|
+
if (isHorizontalStyle(options.formstyle, false)) {
|
|
2785
|
+
if (!fieldInfo.linklabel) {
|
|
2786
|
+
labelHTML += ' for="' + fieldInfo.id + '"';
|
|
2787
|
+
}
|
|
2236
2788
|
if (typeof fieldInfo.labelDefaultClass !== 'undefined') {
|
|
2237
2789
|
// Override default label class (can be empty)
|
|
2238
2790
|
classes += ' ' + fieldInfo.labelDefaultClass;
|
|
@@ -2241,15 +2793,27 @@ var fng;
|
|
|
2241
2793
|
classes += ' col-sm-3';
|
|
2242
2794
|
}
|
|
2243
2795
|
}
|
|
2244
|
-
else if (options.formstyle
|
|
2796
|
+
else if (['inline', 'stacked'].includes(options.formstyle)) {
|
|
2245
2797
|
labelHTML += ' for="' + fieldInfo.id + '"';
|
|
2246
2798
|
classes += ' sr-only';
|
|
2247
2799
|
}
|
|
2248
2800
|
labelHTML += addAllService.addAll(scope, 'Label', null, options) + ' class="' + classes + '">' + fieldInfo.label;
|
|
2249
2801
|
if (addButtonMarkup) {
|
|
2250
|
-
|
|
2802
|
+
var disabledAttrs = handleReadOnlyDisabled(fieldInfo, scope);
|
|
2803
|
+
labelHTML += " <i ".concat(disabledAttrs.join(" "), " id=\"add_").concat(fieldInfo.id, "\" ng-click=\"add('").concat(fieldInfo.name, "', $event)\" class=\"").concat(glyphClass(), "-plus-sign\"></i>");
|
|
2251
2804
|
}
|
|
2252
2805
|
labelHTML += '</label>';
|
|
2806
|
+
if (fieldInfo.linklabel) {
|
|
2807
|
+
var value = '<fng-link fld="' + fieldInfo.name + '" ref="' + fieldInfo.ref + '" text="' + escape(labelHTML) + '"';
|
|
2808
|
+
if (fieldInfo.form) {
|
|
2809
|
+
value += ' form="' + fieldInfo.form + '"';
|
|
2810
|
+
}
|
|
2811
|
+
if (fieldInfo.linktab) {
|
|
2812
|
+
value += ' linktab="' + fieldInfo.linktab + '"';
|
|
2813
|
+
}
|
|
2814
|
+
value += '></fng-link>';
|
|
2815
|
+
labelHTML = value;
|
|
2816
|
+
}
|
|
2253
2817
|
}
|
|
2254
2818
|
return labelHTML;
|
|
2255
2819
|
},
|
|
@@ -2269,13 +2833,27 @@ var fng;
|
|
|
2269
2833
|
else {
|
|
2270
2834
|
sizeClassBS2 = (fieldInfo.size ? ' input-' + fieldInfo.size : '');
|
|
2271
2835
|
}
|
|
2272
|
-
if (options.formstyle
|
|
2836
|
+
if (['inline', 'stacked'].includes(options.formstyle)) {
|
|
2273
2837
|
placeHolder = placeHolder || fieldInfo.label;
|
|
2274
2838
|
}
|
|
2275
|
-
common = 'ng-model="' + modelString + '"'
|
|
2276
|
-
|
|
2839
|
+
common = 'data-ng-model="' + modelString + '"';
|
|
2840
|
+
if (idString) {
|
|
2841
|
+
common += " id=\"".concat(idString, "\"");
|
|
2842
|
+
}
|
|
2843
|
+
if (nameString) {
|
|
2844
|
+
common += " name=\"".concat(nameString, "\"");
|
|
2845
|
+
}
|
|
2846
|
+
else if (idString) {
|
|
2847
|
+
common += " name=\"".concat(idString, "\"");
|
|
2848
|
+
}
|
|
2849
|
+
if (placeHolder) {
|
|
2850
|
+
common += " placeholder=\"".concat(placeHolder, "\"");
|
|
2851
|
+
}
|
|
2277
2852
|
if (fieldInfo.popup) {
|
|
2278
|
-
common +=
|
|
2853
|
+
common += " title=\"".concat(fieldInfo.popup, "\"");
|
|
2854
|
+
}
|
|
2855
|
+
if (fieldInfo.ariaLabel) {
|
|
2856
|
+
common += " aria-label=\"".concat(fieldInfo.ariaLabel, "\"");
|
|
2279
2857
|
}
|
|
2280
2858
|
common += addAllService.addAll(scope, 'Field', null, options);
|
|
2281
2859
|
return {
|
|
@@ -2287,28 +2865,39 @@ var fng;
|
|
|
2287
2865
|
};
|
|
2288
2866
|
},
|
|
2289
2867
|
inputChrome: function inputChrome(value, fieldInfo, options, markupVars) {
|
|
2290
|
-
if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle) && fieldInfo.type !== 'checkbox') {
|
|
2868
|
+
if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle, true) && fieldInfo.type !== 'checkbox') {
|
|
2291
2869
|
value = '<div class="bs3-input ' + markupVars.sizeClassBS3 + '">' + value + '</div>';
|
|
2292
2870
|
}
|
|
2293
2871
|
// Hack to cope with inline help in directives
|
|
2294
2872
|
var inlineHelp = (fieldInfo.helpInline || '') + (fieldInfo.helpinline || '');
|
|
2295
2873
|
if (inlineHelp.length > 0) {
|
|
2296
|
-
|
|
2297
|
-
|
|
2298
|
-
|
|
2299
|
-
|
|
2300
|
-
|
|
2301
|
-
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2874
|
+
var helpMarkup = cssFrameworkService.framework() === 'bs2' ? { el: 'span', cl: 'help-inline' } : { el: 'div', cl: 'help-block' };
|
|
2875
|
+
value += "<".concat(helpMarkup.el, " class=\"").concat(helpMarkup.cl, "\">").concat(inlineHelp, "</").concat(helpMarkup.el, ">");
|
|
2876
|
+
}
|
|
2877
|
+
// this is a dummy tag identifying where the input ends and the messages block (that is only visible when the form field is $dirty)
|
|
2878
|
+
// begins. our caller could replace this tag with anything it needs to insert between these two things.
|
|
2879
|
+
value += "<dms/>";
|
|
2880
|
+
if (!options.noid) {
|
|
2881
|
+
value += "<div ng-if=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$dirty\" class=\"help-block\">") +
|
|
2882
|
+
" <div ng-messages=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$error\">") +
|
|
2883
|
+
' <div ng-messages-include="error-messages.html">' +
|
|
2884
|
+
' </div>' +
|
|
2885
|
+
' </div>' +
|
|
2886
|
+
'</div>';
|
|
2887
|
+
}
|
|
2305
2888
|
if (fieldInfo.help) {
|
|
2306
|
-
value += '<
|
|
2889
|
+
value += '<div class="help-block">' + fieldInfo.help + '</div>';
|
|
2307
2890
|
}
|
|
2308
2891
|
return value;
|
|
2309
2892
|
},
|
|
2310
2893
|
generateSimpleInput: function generateSimpleInput(common, fieldInfo, options) {
|
|
2311
|
-
var result = '<input ' + common + 'type="' + fieldInfo.type + '"';
|
|
2894
|
+
var result = '<input ' + common + 'type="' + fieldInfo.type + '" ';
|
|
2895
|
+
if (!fieldInfo.label && !fieldInfo.ariaLabel) {
|
|
2896
|
+
result += "aria-label=\"".concat(fieldInfo.name.replace(/\./g, ' '), "\" ");
|
|
2897
|
+
}
|
|
2898
|
+
else if (options.subschema) {
|
|
2899
|
+
result += "aria-label=\"".concat(fieldInfo.label ? ($filter('titleCase')(options.subschemaroot) + ' ' + fieldInfo.label) : (fieldInfo.popup || fieldInfo.name.replace(/\./g, ' ')), "\" ");
|
|
2900
|
+
}
|
|
2312
2901
|
if (options.formstyle === 'inline' && cssFrameworkService.framework() === 'bs2' && !fieldInfo.size) {
|
|
2313
2902
|
result += 'class="input-small"';
|
|
2314
2903
|
}
|
|
@@ -2317,7 +2906,7 @@ var fng;
|
|
|
2317
2906
|
},
|
|
2318
2907
|
controlDivClasses: function controlDivClasses(options) {
|
|
2319
2908
|
var result = [];
|
|
2320
|
-
if (isHorizontalStyle(options.formstyle)) {
|
|
2909
|
+
if (isHorizontalStyle(options.formstyle, false)) {
|
|
2321
2910
|
result.push(cssFrameworkService.framework() === 'bs2' ? 'controls' : 'col-sm-9');
|
|
2322
2911
|
}
|
|
2323
2912
|
return result;
|
|
@@ -2328,18 +2917,21 @@ var fng;
|
|
|
2328
2917
|
}
|
|
2329
2918
|
return inputMarkup;
|
|
2330
2919
|
},
|
|
2331
|
-
handleArrayInputAndControlDiv: function handleArrayInputAndControlDiv(inputMarkup, controlDivClasses, info, options) {
|
|
2332
|
-
var
|
|
2333
|
-
|
|
2334
|
-
|
|
2335
|
-
|
|
2336
|
-
|
|
2337
|
-
|
|
2338
|
-
|
|
2339
|
-
|
|
2340
|
-
|
|
2341
|
-
}
|
|
2920
|
+
handleArrayInputAndControlDiv: function handleArrayInputAndControlDiv(inputMarkup, controlDivClasses, scope, info, options) {
|
|
2921
|
+
var indentStr = cssFrameworkService.framework() === 'bs3' ? 'ng-class="skipCols($index)" ' : "";
|
|
2922
|
+
var arrayStr = (options.model || 'record') + '.' + info.name;
|
|
2923
|
+
var result = "";
|
|
2924
|
+
result += '<div id="' + info.id + 'List" class="' + controlDivClasses.join(' ') + '" ' + indentStr + ' ng-repeat="arrayItem in ' + arrayStr + ' track by $index">';
|
|
2925
|
+
var disabledAttrs = handleReadOnlyDisabled(info, scope);
|
|
2926
|
+
var removeBtn = info.type !== 'link'
|
|
2927
|
+
? "<i ".concat(disabledAttrs.join(" "), " ng-click=\"remove('").concat(info.name, "', $index, $event)\" id=\"remove_").concat(info.id, "_{{$index}}\" class=\"").concat(glyphClass(), "-minus-sign\"></i>")
|
|
2928
|
+
: "";
|
|
2929
|
+
result += inputMarkup.replace("<dms/>", removeBtn);
|
|
2342
2930
|
result += '</div>';
|
|
2931
|
+
indentStr = cssFrameworkService.framework() === 'bs3' ? 'ng-class="skipCols(' + arrayStr + '.length)" ' : "";
|
|
2932
|
+
if (info.help) {
|
|
2933
|
+
result += '<div class="array-help-block ' + controlDivClasses.join(' ') + '" ' + indentStr + ' id="empty' + info.id + 'ListHelpBlock">' + info.help + '</div>';
|
|
2934
|
+
}
|
|
2343
2935
|
return result;
|
|
2344
2936
|
},
|
|
2345
2937
|
addTextInputMarkup: function addTextInputMarkup(allInputsVars, fieldInfo, requiredStr) {
|
|
@@ -2351,9 +2943,12 @@ var fng;
|
|
|
2351
2943
|
if (fieldInfo.add) {
|
|
2352
2944
|
result += ' ' + fieldInfo.add + ' ';
|
|
2353
2945
|
}
|
|
2354
|
-
result += requiredStr
|
|
2946
|
+
result += requiredStr;
|
|
2355
2947
|
return result;
|
|
2356
|
-
}
|
|
2948
|
+
},
|
|
2949
|
+
handleReadOnlyDisabled: handleReadOnlyDisabled,
|
|
2950
|
+
generateArrayElementIdString: generateArrayElementIdString,
|
|
2951
|
+
genDisableableAncestorStr: genDisableableAncestorStr
|
|
2357
2952
|
};
|
|
2358
2953
|
}
|
|
2359
2954
|
services.formMarkupHelper = formMarkupHelper;
|
|
@@ -2392,103 +2987,260 @@ var fng;
|
|
|
2392
2987
|
/*@ngInject*/
|
|
2393
2988
|
pluginHelper.$inject = ["formMarkupHelper"];
|
|
2394
2989
|
function pluginHelper(formMarkupHelper) {
|
|
2395
|
-
|
|
2396
|
-
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2990
|
+
function internalGenDisabledAttrs(scope, id, processedAttrs, idSuffix, params) {
|
|
2991
|
+
// Though id will already have the value of idSuffix appended, processedAttrs.info.name will not.
|
|
2992
|
+
// For handleReadOnlyDisabled() to disable "sub-elements" included in a directive template with an idsuffix when their
|
|
2993
|
+
// 'parent' field is disabled, we need the name to include that suffix as if it were an additional level
|
|
2994
|
+
// of field nesting.
|
|
2995
|
+
var name = processedAttrs.info.name;
|
|
2996
|
+
if (idSuffix) {
|
|
2997
|
+
if (params === null || params === void 0 ? void 0 : params.nonUniqueIdSuffix) {
|
|
2998
|
+
// Generally, when genIdAndDisabledStr is called from a directive, the idSuffix will be something like "select"
|
|
2999
|
+
// or "hasValueCheckbox" (thus enabling a single directive to create a template that includes more than one form
|
|
3000
|
+
// element - such as a checkbox and an input - each of which has a unique id).
|
|
3001
|
+
// Where a directive is responsible for creating markup for an whole array of elements, it is likely to include an
|
|
3002
|
+
// ng-repeat in the template that it generates, and in this case, the idSuffix that it passes to genIdAndDisabledStr
|
|
3003
|
+
// will probably include a reference to $index to ensure uniqueness.
|
|
3004
|
+
// Where idSuffix /does/ contain a reference to $index, the directive should provide a version of the idSuffix
|
|
3005
|
+
// in the params object which does NOT include this.
|
|
3006
|
+
// This is what we need to use for the ng-disabled/ng-readonly expression.
|
|
3007
|
+
// (ReallyCare development hint: for an example of where this is needed, see or-opts.ts.)
|
|
3008
|
+
id = id.replace(idSuffix, params.nonUniqueIdSuffix);
|
|
3009
|
+
name += ".".concat(params.nonUniqueIdSuffix);
|
|
3010
|
+
}
|
|
3011
|
+
else {
|
|
3012
|
+
name += ".".concat(idSuffix);
|
|
3013
|
+
}
|
|
3014
|
+
}
|
|
3015
|
+
var attrs = formMarkupHelper.handleReadOnlyDisabled({
|
|
3016
|
+
id: id,
|
|
3017
|
+
name: name,
|
|
3018
|
+
nonUniqueId: processedAttrs.info.nonuniqueid,
|
|
3019
|
+
readonly: processedAttrs.info.readonly
|
|
3020
|
+
}, scope);
|
|
3021
|
+
// some types of control (such as ui-select) don't deal correctly with a DISABLED attribute and
|
|
3022
|
+
// need ng-disabled, even when the expression is simply "true"
|
|
3023
|
+
if (params === null || params === void 0 ? void 0 : params.forceNg) {
|
|
3024
|
+
for (var i = 0; i < attrs.length; i++) {
|
|
3025
|
+
if (attrs[i].toLowerCase().trim() === "disabled") {
|
|
3026
|
+
attrs[i] = 'ng-disabled="true"';
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
return attrs;
|
|
3031
|
+
}
|
|
3032
|
+
function internalGenDisabledStr(scope, id, processedAttrs, idSuffix, params) {
|
|
3033
|
+
return internalGenDisabledAttrs(scope, id, processedAttrs, idSuffix, params).join(" ");
|
|
3034
|
+
}
|
|
3035
|
+
// text surrounded by @@ @@ is assumed to be something that can have a pseudonym. We'll rely
|
|
3036
|
+
// upon the relevant controller assigning a pseudo() function to baseScope.
|
|
3037
|
+
function handlePseudos(str) {
|
|
3038
|
+
if (!str) {
|
|
3039
|
+
return str;
|
|
3040
|
+
}
|
|
3041
|
+
var result = str;
|
|
3042
|
+
while (result.includes("@@")) {
|
|
3043
|
+
result = result.replace("@@", "{{ baseScope.pseudo('");
|
|
3044
|
+
result = result.replace("@@", "', true) }}");
|
|
3045
|
+
}
|
|
3046
|
+
return result;
|
|
3047
|
+
}
|
|
3048
|
+
function makeIdStringUniqueForArrayElements(scope, processedAttrs, idString) {
|
|
3049
|
+
if (formMarkupHelper.isArrayElement(scope, processedAttrs.info, processedAttrs.options)) {
|
|
3050
|
+
return formMarkupHelper.generateArrayElementIdString(idString, processedAttrs.info, processedAttrs.options);
|
|
3051
|
+
}
|
|
3052
|
+
else {
|
|
3053
|
+
return idString;
|
|
3054
|
+
}
|
|
3055
|
+
}
|
|
3056
|
+
function internalGenIdString(scope, processedAttrs, suffix, makeUniqueForArrayElements) {
|
|
3057
|
+
var result = processedAttrs.info.id;
|
|
3058
|
+
if (suffix) {
|
|
3059
|
+
if (!suffix.startsWith("_")) {
|
|
3060
|
+
result += "_";
|
|
3061
|
+
}
|
|
3062
|
+
result += suffix;
|
|
3063
|
+
}
|
|
3064
|
+
if (makeUniqueForArrayElements) {
|
|
3065
|
+
result = makeIdStringUniqueForArrayElements(scope, processedAttrs, result);
|
|
3066
|
+
}
|
|
3067
|
+
return result;
|
|
3068
|
+
}
|
|
3069
|
+
function internalGenDateTimePickerDisabledStr(scope, processedAttrs, idSuffix, idString) {
|
|
3070
|
+
var rawDisabledAttrs = internalGenDisabledAttrs(scope, idString, processedAttrs, idSuffix, { forceNg: true });
|
|
3071
|
+
// first, we need to convert the 'disabled' attribute(s) (those which might actually cause the element to be
|
|
3072
|
+
// disabled - found in rawDisabledAttrs[0]) into something that the datetime picker understands.
|
|
3073
|
+
var rawDisabledStr = rawDisabledAttrs[0];
|
|
3074
|
+
var disabledStr = "";
|
|
3075
|
+
// disabledStr might now include an ng-disabled attribute. To disable both the date and time inputs, we need to
|
|
3076
|
+
// take the value of that attribute and wrap it up as two new attributes: "disabledDate" and "readonlyTime"
|
|
3077
|
+
// (which is what the datetimepicker directive is expecting to receive)
|
|
3078
|
+
if (rawDisabledStr) {
|
|
3079
|
+
// disabledStr should contain either 'ng-disabled="xxxx"' or 'ng-readonly="yyyy"', or both.
|
|
3080
|
+
// the values of xxxx and yyyy could be more-or-less anything, and certainly they could include = or ", which
|
|
3081
|
+
// makes parsing hard
|
|
3082
|
+
// our strategy will be to re-format disabledStr as if it was the string representation of an object, and
|
|
3083
|
+
// then parse it. we can then refer to the ng-disabled and ng-readonly attributes of the parsed object.
|
|
3084
|
+
// in the future, perhaps ng-disabled and ng-readonly will be changed to data-ng-disabled and data-ng-readonly
|
|
3085
|
+
rawDisabledStr = rawDisabledStr.replace("data-ng-disabled", "ng-disabled");
|
|
3086
|
+
rawDisabledStr = rawDisabledStr.replace("data-ng-readonly", "ng-readonly");
|
|
3087
|
+
rawDisabledStr = rawDisabledStr.replace("ng-disabled=", '"ng-disabled":');
|
|
3088
|
+
rawDisabledStr = rawDisabledStr.replace("ng-readonly=", '"ng-readonly":');
|
|
3089
|
+
try {
|
|
3090
|
+
rawDisabledStr = "{ ".concat(rawDisabledStr, " }");
|
|
3091
|
+
var disabledObj = JSON.parse(rawDisabledStr);
|
|
3092
|
+
rawDisabledStr = disabledObj["ng-disabled"];
|
|
3093
|
+
// cannot see a way to sensibly deal with both ng-disabled and ng-readonly. Let's just ignore the ng-readonly
|
|
3094
|
+
// for now - with the way handleReadOnlyDisabled is currently written, this means we'll be unable to fully
|
|
3095
|
+
// support a datetime field with a string-typed "readonly" attribute and where fngAngular's elemSecurityFuncBinding
|
|
3096
|
+
// option is set up to "one-time" or "normal".
|
|
3097
|
+
if (rawDisabledStr) {
|
|
3098
|
+
disabledStr = "disabledDate=\"".concat(rawDisabledStr, "\" readonlyTime=\"").concat(rawDisabledStr, "\"");
|
|
2401
3099
|
}
|
|
2402
|
-
|
|
2403
|
-
|
|
3100
|
+
}
|
|
3101
|
+
catch (e) {
|
|
3102
|
+
// give up
|
|
3103
|
+
}
|
|
3104
|
+
}
|
|
3105
|
+
// finally, we should add the 'disableable' attribute(s), which might be present in rawDisabledAttrs[1] (regardless
|
|
3106
|
+
// of whether or not the datetime picker is actually disabled) to indicate that it potentially could be
|
|
3107
|
+
return disabledStr + " " + rawDisabledAttrs[1];
|
|
3108
|
+
}
|
|
3109
|
+
function extractFromAttr(attr, directiveName) {
|
|
3110
|
+
function deserialize(str) {
|
|
3111
|
+
var retVal = str.replace(/"/g, '"');
|
|
3112
|
+
if (retVal === "true") {
|
|
3113
|
+
return true;
|
|
3114
|
+
}
|
|
3115
|
+
else if (retVal === "false") {
|
|
3116
|
+
return false;
|
|
3117
|
+
}
|
|
3118
|
+
else {
|
|
3119
|
+
var num = parseFloat(retVal);
|
|
3120
|
+
if (!isNaN(num) && isFinite(num)) {
|
|
3121
|
+
return num;
|
|
2404
3122
|
}
|
|
2405
|
-
else
|
|
2406
|
-
|
|
3123
|
+
else {
|
|
3124
|
+
return retVal;
|
|
2407
3125
|
}
|
|
2408
|
-
return retVal;
|
|
2409
3126
|
}
|
|
2410
|
-
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
|
|
2418
|
-
|
|
2419
|
-
|
|
2420
|
-
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
|
|
3127
|
+
}
|
|
3128
|
+
var info = {};
|
|
3129
|
+
var options = { formStyle: attr.formstyle };
|
|
3130
|
+
var directiveOptions = {};
|
|
3131
|
+
var directiveNameLength = directiveName ? directiveName.length : 0;
|
|
3132
|
+
var lcDirectiveName = directiveName === null || directiveName === void 0 ? void 0 : directiveName.toLowerCase();
|
|
3133
|
+
for (var prop in attr) {
|
|
3134
|
+
if (attr.hasOwnProperty(prop)) {
|
|
3135
|
+
var lcProp = prop.toLowerCase();
|
|
3136
|
+
if (lcProp.slice(0, 6) === "fngfld") {
|
|
3137
|
+
info[lcProp.slice(6)] = deserialize(attr[prop]);
|
|
3138
|
+
}
|
|
3139
|
+
else if (lcProp.slice(0, 6) === "fngopt") {
|
|
3140
|
+
options[lcProp.slice(6)] = deserialize(attr[prop]);
|
|
3141
|
+
}
|
|
3142
|
+
else if (directiveName && lcProp.slice(0, directiveNameLength) === lcDirectiveName) {
|
|
3143
|
+
directiveOptions[_.kebabCase(prop.slice(directiveNameLength))] = deserialize(attr[prop]);
|
|
2425
3144
|
}
|
|
2426
3145
|
}
|
|
2427
|
-
|
|
2428
|
-
}
|
|
2429
|
-
|
|
2430
|
-
|
|
2431
|
-
|
|
2432
|
-
|
|
2433
|
-
|
|
2434
|
-
|
|
2435
|
-
|
|
2436
|
-
|
|
2437
|
-
|
|
3146
|
+
}
|
|
3147
|
+
var result = { info: info, options: options, directiveOptions: directiveOptions };
|
|
3148
|
+
// any part of the help text or label that is surrounded by @@ @@ is assumed to be something that can have
|
|
3149
|
+
// a pseudonym. We'll be relying upon the parent controller assigning a pseudo() function to baseScope to
|
|
3150
|
+
// actually perform the translation.
|
|
3151
|
+
// TODO - do this better when fng is re-written!
|
|
3152
|
+
result.info.help = handlePseudos(result.info.help);
|
|
3153
|
+
result.info.label = handlePseudos(result.info.label);
|
|
3154
|
+
return result;
|
|
3155
|
+
}
|
|
3156
|
+
function genIdAndDisabledStr(scope, processedAttrs, idSuffix, params) {
|
|
3157
|
+
var idStr = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3158
|
+
var uniqueIdStr = makeIdStringUniqueForArrayElements(scope, processedAttrs, idStr);
|
|
3159
|
+
return "id=\"".concat(uniqueIdStr, "\" ").concat(internalGenDisabledStr(scope, idStr, processedAttrs, idSuffix, params));
|
|
3160
|
+
}
|
|
3161
|
+
return {
|
|
3162
|
+
extractFromAttr: extractFromAttr,
|
|
3163
|
+
buildInputMarkup: function buildInputMarkup(scope, attrs, params, generateInputControl) {
|
|
3164
|
+
var processedAttrs = params.processedAttrs || extractFromAttr(attrs, "");
|
|
3165
|
+
var info = {};
|
|
3166
|
+
if (!params.ignoreFieldInfoFromAttrs) {
|
|
3167
|
+
Object.assign(info, processedAttrs.info);
|
|
2438
3168
|
}
|
|
2439
|
-
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
3169
|
+
if (params.fieldInfoOverrides) {
|
|
3170
|
+
Object.assign(info, params.fieldInfoOverrides);
|
|
3171
|
+
}
|
|
3172
|
+
var options = Object.assign({}, processedAttrs.options, params.optionOverrides);
|
|
3173
|
+
var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
|
|
3174
|
+
if (fieldChrome.omit) {
|
|
3175
|
+
return "";
|
|
2443
3176
|
}
|
|
2444
|
-
|
|
3177
|
+
var controlDivClasses = formMarkupHelper.controlDivClasses(options);
|
|
3178
|
+
var elementHtml = fieldChrome.template + formMarkupHelper.label(scope, info, params.addButtons, options);
|
|
3179
|
+
var idString = info.id;
|
|
3180
|
+
if (info.array || options.subschema) {
|
|
3181
|
+
idString = formMarkupHelper.generateArrayElementIdString(idString, info, options);
|
|
3182
|
+
}
|
|
3183
|
+
var modelString = params.addButtons
|
|
3184
|
+
? "arrayItem" + (params.needsX ? ".x" : "")
|
|
3185
|
+
: attrs.model + "." + info.name;
|
|
3186
|
+
var nameString = info.name;
|
|
3187
|
+
if (options.subschema && info.name.indexOf(".") !== -1) {
|
|
2445
3188
|
// Schema handling - need to massage the ngModel and the id
|
|
2446
|
-
var modelBase = model +
|
|
2447
|
-
var compoundName = info.name;
|
|
3189
|
+
var modelBase = attrs.model + ".";
|
|
2448
3190
|
var root = options.subschemaroot;
|
|
2449
|
-
var lastPart =
|
|
2450
|
-
modelString = modelBase;
|
|
2451
|
-
if (options.
|
|
2452
|
-
modelString
|
|
2453
|
-
|
|
3191
|
+
var lastPart = info.name.slice(root.length + 1);
|
|
3192
|
+
modelString = modelBase + root;
|
|
3193
|
+
if (options.subkey) {
|
|
3194
|
+
idString = modelString.slice(modelBase.length).replace(/\./g, "-") + "-subkey" + options.subkeyno + "-" + lastPart;
|
|
3195
|
+
modelString += "[" + "$_arrayOffset_" + root.replace(/\./g, "_") + "_" + options.subkeyno + "]." + lastPart;
|
|
2454
3196
|
}
|
|
2455
3197
|
else {
|
|
2456
|
-
modelString +=
|
|
2457
|
-
|
|
2458
|
-
idString = modelString.slice(modelBase.length).replace(/\./g, '-') + '-subkey' + options.subkeyno + '-' + lastPart;
|
|
2459
|
-
modelString += '[' + '$_arrayOffset_' + root.replace(/\./g, '_') + '_' + options.subkeyno + '].' + lastPart;
|
|
2460
|
-
}
|
|
2461
|
-
else {
|
|
2462
|
-
modelString += '[$index].' + lastPart;
|
|
2463
|
-
idString = null;
|
|
2464
|
-
nameString = compoundName.replace(/\./g, '-');
|
|
2465
|
-
}
|
|
3198
|
+
modelString += "[$index]." + lastPart;
|
|
3199
|
+
nameString = info.name.replace(/\./g, "-");
|
|
2466
3200
|
}
|
|
2467
3201
|
}
|
|
2468
3202
|
var buildingBlocks = formMarkupHelper.allInputsVars(scope, info, options, modelString, idString, nameString);
|
|
2469
3203
|
buildingBlocks.modelString = modelString;
|
|
2470
|
-
|
|
3204
|
+
buildingBlocks.disableableAncestorStr = formMarkupHelper.genDisableableAncestorStr(info.id);
|
|
3205
|
+
// defer to the calling directive to generate the markup for the input(s)
|
|
3206
|
+
var inputHtml = generateInputControl(buildingBlocks);
|
|
3207
|
+
// wrap this in a div that puts it into the correct bootstrap 'column' and adds validation messages and help text
|
|
3208
|
+
var wrappedInputHtml = formMarkupHelper.inputChrome(inputHtml, info, options, buildingBlocks);
|
|
3209
|
+
// further wrap this to add the control div classes, and in the case of an array, the button that allows array elements to be removed
|
|
3210
|
+
if (params.addButtons) {
|
|
3211
|
+
elementHtml += formMarkupHelper.handleArrayInputAndControlDiv(wrappedInputHtml, controlDivClasses, scope, info, options);
|
|
3212
|
+
}
|
|
3213
|
+
else {
|
|
3214
|
+
elementHtml += formMarkupHelper.handleInputAndControlDiv(wrappedInputHtml, controlDivClasses);
|
|
3215
|
+
}
|
|
2471
3216
|
elementHtml += fieldChrome.closeTag;
|
|
2472
3217
|
return elementHtml;
|
|
2473
3218
|
},
|
|
2474
|
-
|
|
2475
|
-
|
|
2476
|
-
|
|
2477
|
-
|
|
2478
|
-
|
|
2479
|
-
|
|
2480
|
-
|
|
2481
|
-
|
|
2482
|
-
|
|
2483
|
-
|
|
2484
|
-
|
|
2485
|
-
|
|
2486
|
-
|
|
2487
|
-
|
|
2488
|
-
|
|
2489
|
-
|
|
2490
|
-
|
|
2491
|
-
|
|
3219
|
+
genIdString: function genIdString(scope, processedAttrs, idSuffix) {
|
|
3220
|
+
return internalGenIdString(scope, processedAttrs, idSuffix, true);
|
|
3221
|
+
},
|
|
3222
|
+
genDisabledStr: function genDisabledStr(scope, processedAttrs, idSuffix, params) {
|
|
3223
|
+
var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3224
|
+
return internalGenDisabledStr(scope, idString, processedAttrs, idSuffix, params);
|
|
3225
|
+
},
|
|
3226
|
+
genIdAndDisabledStr: genIdAndDisabledStr,
|
|
3227
|
+
genDateTimePickerDisabledStr: function genDateTimePickerDisabledStr(scope, processedAttrs, idSuffix) {
|
|
3228
|
+
var idString = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3229
|
+
return internalGenDateTimePickerDisabledStr(scope, processedAttrs, idSuffix, idString);
|
|
3230
|
+
},
|
|
3231
|
+
genDateTimePickerIdAndDisabledStr: function genDateTimePickerIdAndDisabledStr(scope, processedAttrs, idSuffix) {
|
|
3232
|
+
var idStr = internalGenIdString(scope, processedAttrs, idSuffix, false);
|
|
3233
|
+
var uniqueIdStr = makeIdStringUniqueForArrayElements(scope, processedAttrs, idStr);
|
|
3234
|
+
return "id=\"".concat(uniqueIdStr, "\" ").concat(internalGenDateTimePickerDisabledStr(scope, processedAttrs, idSuffix, idStr));
|
|
3235
|
+
},
|
|
3236
|
+
genUiSelectIdAndDisabledStr: function genUiSelectIdAndDisabledStr(scope, processedAttrs, idSuffix) {
|
|
3237
|
+
// ui-select won't be disabled when a simple DISABLED attribute is provided - it requires
|
|
3238
|
+
// ng-disabled even when the value is simply "true"
|
|
3239
|
+
return genIdAndDisabledStr(scope, processedAttrs, idSuffix, { forceNg: true });
|
|
3240
|
+
},
|
|
3241
|
+
handlePseudos: handlePseudos,
|
|
3242
|
+
genDisableableAncestorStr: function genDisableableAncestorStr(processedAttrs) {
|
|
3243
|
+
return formMarkupHelper.genDisableableAncestorStr(processedAttrs.info.id);
|
|
2492
3244
|
}
|
|
2493
3245
|
};
|
|
2494
3246
|
}
|
|
@@ -2507,26 +3259,32 @@ var fng;
|
|
|
2507
3259
|
*
|
|
2508
3260
|
*/
|
|
2509
3261
|
/*@ngInject*/
|
|
2510
|
-
recordHandler.$inject = ["$
|
|
2511
|
-
function recordHandler($
|
|
3262
|
+
recordHandler.$inject = ["$location", "$window", "$filter", "$timeout", "$sce", "routingService", "cssFrameworkService", "SubmissionsService", "SchemasService"];
|
|
3263
|
+
function recordHandler($location, $window, $filter, $timeout, $sce, routingService, cssFrameworkService, SubmissionsService, SchemasService) {
|
|
2512
3264
|
// TODO: Put this in a service
|
|
2513
3265
|
var makeMongoId = function (rnd) {
|
|
2514
3266
|
if (rnd === void 0) { rnd = function (r16) { return Math.floor(r16).toString(16); }; }
|
|
2515
|
-
return rnd(Date.now() / 1000) +
|
|
3267
|
+
return rnd(Date.now() / 1000) + " ".repeat(16).replace(/./g, function () { return rnd(Math.random() * 16); });
|
|
2516
3268
|
};
|
|
3269
|
+
function _handleCancel(resp) {
|
|
3270
|
+
if (["cancel", "backdrop click", "escape key press"].indexOf(resp) === -1) {
|
|
3271
|
+
throw resp;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
2517
3274
|
var suffixCleanId = function suffixCleanId(inst, suffix) {
|
|
2518
|
-
return (inst.id ||
|
|
3275
|
+
return (inst.id || "f_" + inst.name).replace(/\./g, "_") + suffix;
|
|
2519
3276
|
};
|
|
2520
|
-
var walkTree = function (object, fieldname, element) {
|
|
3277
|
+
var walkTree = function (object, fieldname, element, insertIntermediateObjects) {
|
|
2521
3278
|
// Walk through subdocs to find the required key
|
|
2522
3279
|
// for instance walkTree(master,'address.street.number',element)
|
|
2523
3280
|
// called by getData and setData
|
|
3281
|
+
if (insertIntermediateObjects === void 0) { insertIntermediateObjects = false; }
|
|
2524
3282
|
// element is used when accessing in the context of a input, as the id (like exams-2-grader)
|
|
2525
3283
|
// gives us the element of an array (one level down only for now). Leaving element blank returns the whole array
|
|
2526
|
-
var parts = fieldname.split(
|
|
3284
|
+
var parts = fieldname.split("."), higherLevels = parts.length - 1, workingRec = object;
|
|
2527
3285
|
for (var i = 0; i < higherLevels; i++) {
|
|
2528
3286
|
if (!workingRec) {
|
|
2529
|
-
throw new Error("walkTree failed: Object = "
|
|
3287
|
+
throw new Error("walkTree failed: Object = ".concat(object, ", fieldname = ").concat(fieldname, ", i = ").concat(i));
|
|
2530
3288
|
}
|
|
2531
3289
|
if (angular.isArray(workingRec)) {
|
|
2532
3290
|
workingRec = _.map(workingRec, function (obj) {
|
|
@@ -2534,18 +3292,21 @@ var fng;
|
|
|
2534
3292
|
});
|
|
2535
3293
|
}
|
|
2536
3294
|
else {
|
|
3295
|
+
if (insertIntermediateObjects && !workingRec[parts[i]]) {
|
|
3296
|
+
workingRec[parts[i]] = {};
|
|
3297
|
+
}
|
|
2537
3298
|
workingRec = workingRec[parts[i]];
|
|
2538
3299
|
}
|
|
2539
|
-
if (angular.isArray(workingRec) && typeof element !==
|
|
2540
|
-
if (element.scope && typeof element.scope ===
|
|
3300
|
+
if (angular.isArray(workingRec) && typeof element !== "undefined") {
|
|
3301
|
+
if (element.scope && typeof element.scope === "function") {
|
|
2541
3302
|
// If we come across an array we need to find the correct position, if we have an element
|
|
2542
3303
|
workingRec = workingRec[element.scope().$index];
|
|
2543
3304
|
}
|
|
2544
|
-
else if (typeof element ===
|
|
3305
|
+
else if (typeof element === "number") {
|
|
2545
3306
|
workingRec = workingRec[element];
|
|
2546
3307
|
}
|
|
2547
3308
|
else {
|
|
2548
|
-
throw new Error(
|
|
3309
|
+
throw new Error("Unsupported element type in walkTree " + fieldname);
|
|
2549
3310
|
}
|
|
2550
3311
|
}
|
|
2551
3312
|
if (!workingRec) {
|
|
@@ -2558,15 +3319,20 @@ var fng;
|
|
|
2558
3319
|
};
|
|
2559
3320
|
};
|
|
2560
3321
|
var setData = function setData(object, fieldname, element, value) {
|
|
2561
|
-
var leafData = walkTree(object, fieldname, element);
|
|
3322
|
+
var leafData = walkTree(object, fieldname, element, !!value);
|
|
2562
3323
|
if (leafData.lastObject && leafData.key) {
|
|
2563
|
-
if (
|
|
2564
|
-
|
|
2565
|
-
leafData.lastObject
|
|
3324
|
+
if (value) {
|
|
3325
|
+
if (angular.isArray(leafData.lastObject)) {
|
|
3326
|
+
for (var i = 0; i < leafData.lastObject.length; i++) {
|
|
3327
|
+
leafData.lastObject[i][leafData.key] = value[i];
|
|
3328
|
+
}
|
|
3329
|
+
}
|
|
3330
|
+
else {
|
|
3331
|
+
leafData.lastObject[leafData.key] = value;
|
|
2566
3332
|
}
|
|
2567
3333
|
}
|
|
2568
3334
|
else {
|
|
2569
|
-
leafData.lastObject[leafData.key]
|
|
3335
|
+
delete leafData.lastObject[leafData.key];
|
|
2570
3336
|
}
|
|
2571
3337
|
}
|
|
2572
3338
|
};
|
|
@@ -2585,11 +3351,17 @@ var fng;
|
|
|
2585
3351
|
}
|
|
2586
3352
|
return retVal;
|
|
2587
3353
|
};
|
|
2588
|
-
var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState) {
|
|
3354
|
+
var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState, ignoreDirty) {
|
|
3355
|
+
if (ignoreDirty === void 0) { ignoreDirty = false; }
|
|
2589
3356
|
// Update the master and the record with the lookup values, master first
|
|
2590
|
-
if (!$scope.topLevelFormName || $scope[$scope.topLevelFormName].$pristine) {
|
|
3357
|
+
if (!$scope.topLevelFormName || ($scope[$scope.topLevelFormName] && (ignoreDirty || $scope[$scope.topLevelFormName].$pristine))) {
|
|
2591
3358
|
updateObject(schemaElement.name, ctrlState.master, function (value) {
|
|
2592
|
-
|
|
3359
|
+
if (typeof value == "object" && value.id) {
|
|
3360
|
+
return value;
|
|
3361
|
+
}
|
|
3362
|
+
else {
|
|
3363
|
+
return convertForeignKeys(schemaElement, value, $scope[suffixCleanId(schemaElement, "Options")], $scope[suffixCleanId(schemaElement, "_ids")]);
|
|
3364
|
+
}
|
|
2593
3365
|
});
|
|
2594
3366
|
// Then copy the converted keys from master into record
|
|
2595
3367
|
var newVal = getData(ctrlState.master, schemaElement.name);
|
|
@@ -2600,38 +3372,29 @@ var fng;
|
|
|
2600
3372
|
};
|
|
2601
3373
|
// Split a field name into the next level and all following levels
|
|
2602
3374
|
function splitFieldName(aFieldName) {
|
|
2603
|
-
var nesting = aFieldName.split(
|
|
3375
|
+
var nesting = aFieldName.split("."), result = [nesting[0]];
|
|
2604
3376
|
if (nesting.length > 1) {
|
|
2605
|
-
result.push(nesting.slice(1).join(
|
|
3377
|
+
result.push(nesting.slice(1).join("."));
|
|
2606
3378
|
}
|
|
2607
3379
|
return result;
|
|
2608
3380
|
}
|
|
2609
|
-
var getListData = function getListData(
|
|
3381
|
+
var getListData = function getListData(record, fieldName, listSchema, $scope) {
|
|
2610
3382
|
if (listSchema === void 0) { listSchema = null; }
|
|
2611
|
-
var retVal = record;
|
|
2612
|
-
var nests = fieldName.split('.');
|
|
2613
|
-
for (var i = 0; i < nests.length; i++) {
|
|
2614
|
-
if (retVal !== undefined && retVal !== null && nests && nests[i]) {
|
|
2615
|
-
retVal = retVal[nests[i]];
|
|
2616
|
-
}
|
|
2617
|
-
}
|
|
2618
|
-
if (retVal === undefined) {
|
|
2619
|
-
retVal = '';
|
|
2620
|
-
}
|
|
3383
|
+
var retVal = getData(record, fieldName) || "";
|
|
2621
3384
|
if (retVal && listSchema) {
|
|
2622
3385
|
// Convert list fields as per instructions in params (ideally should be the same as what is found in data_form getListFields
|
|
2623
|
-
var schemaElm = _.find(listSchema, function (elm) { return (elm[
|
|
3386
|
+
var schemaElm = _.find(listSchema, function (elm) { return (elm["name"] === fieldName); });
|
|
2624
3387
|
if (schemaElm) {
|
|
2625
|
-
switch (schemaElm[
|
|
3388
|
+
switch (schemaElm["params"]) {
|
|
2626
3389
|
case undefined:
|
|
2627
3390
|
break;
|
|
2628
|
-
case
|
|
3391
|
+
case "timestamp":
|
|
2629
3392
|
var timestamp = retVal.toString().substring(0, 8);
|
|
2630
3393
|
var date = new Date(parseInt(timestamp, 16) * 1000);
|
|
2631
|
-
retVal = date.toLocaleDateString() +
|
|
3394
|
+
retVal = date.toLocaleDateString() + " " + date.toLocaleTimeString();
|
|
2632
3395
|
break;
|
|
2633
3396
|
default:
|
|
2634
|
-
retVal = $scope.dataEventFunctions[schemaElm[
|
|
3397
|
+
retVal = $scope.dataEventFunctions[schemaElm["params"]](record);
|
|
2635
3398
|
}
|
|
2636
3399
|
}
|
|
2637
3400
|
}
|
|
@@ -2648,7 +3411,7 @@ var fng;
|
|
|
2648
3411
|
if (angular.isArray(theValue)) {
|
|
2649
3412
|
for (var i = theValue.length - 1; i >= 0; i--) {
|
|
2650
3413
|
var type = typeof theValue[i];
|
|
2651
|
-
if (type ===
|
|
3414
|
+
if (type === "undefined" || (type === "object" && Object.keys(theValue[i]).length === 0)) {
|
|
2652
3415
|
theValue.splice(i, 1);
|
|
2653
3416
|
}
|
|
2654
3417
|
}
|
|
@@ -2670,8 +3433,8 @@ var fng;
|
|
|
2670
3433
|
}
|
|
2671
3434
|
// Set up the lookup lists (value and id) on the scope for an internal lookup. Called by convertToAngularModel and $watch
|
|
2672
3435
|
function setUpInternalLookupLists($scope, options, ids, newVal, valueAttrib) {
|
|
2673
|
-
var optionsArray = (typeof options ===
|
|
2674
|
-
var idsArray = (typeof ids ===
|
|
3436
|
+
var optionsArray = (typeof options === "string" ? $scope[options] : options);
|
|
3437
|
+
var idsArray = (typeof ids === "string" ? $scope[ids] : ids);
|
|
2675
3438
|
optionsArray.length = 0;
|
|
2676
3439
|
idsArray.length = 0;
|
|
2677
3440
|
if (!!newVal && (newVal.length > 0)) {
|
|
@@ -2693,10 +3456,10 @@ var fng;
|
|
|
2693
3456
|
result = true;
|
|
2694
3457
|
}
|
|
2695
3458
|
else if (!aSchema.directive) {
|
|
2696
|
-
if (aSchema.type ===
|
|
3459
|
+
if (aSchema.type === "text") {
|
|
2697
3460
|
result = true;
|
|
2698
3461
|
}
|
|
2699
|
-
else if (aSchema.type ===
|
|
3462
|
+
else if (aSchema.type === "select" && !aSchema.ids) {
|
|
2700
3463
|
result = true;
|
|
2701
3464
|
}
|
|
2702
3465
|
}
|
|
@@ -2706,7 +3469,7 @@ var fng;
|
|
|
2706
3469
|
function getConversionObject(scope, entryName, schemaName) {
|
|
2707
3470
|
var conversions = scope.conversions;
|
|
2708
3471
|
if (schemaName) {
|
|
2709
|
-
conversions = conversions
|
|
3472
|
+
conversions = getData(conversions, schemaName) || {};
|
|
2710
3473
|
}
|
|
2711
3474
|
return conversions[entryName];
|
|
2712
3475
|
}
|
|
@@ -2717,46 +3480,62 @@ var fng;
|
|
|
2717
3480
|
for (var i = 0; i < schema.length; i++) {
|
|
2718
3481
|
var schemaEntry = schema[i];
|
|
2719
3482
|
var fieldName = schemaEntry.name.slice(prefixLength);
|
|
3483
|
+
if (!fieldName.length) {
|
|
3484
|
+
fieldName = schemaEntry.name.split('.').pop();
|
|
3485
|
+
}
|
|
2720
3486
|
var fieldValue = getData(anObject, fieldName);
|
|
3487
|
+
if (schemaEntry.intType === 'date' && typeof fieldValue === 'string') {
|
|
3488
|
+
setData(anObject, fieldName, null, new Date(fieldValue));
|
|
3489
|
+
}
|
|
2721
3490
|
if (schemaEntry.schema) {
|
|
2722
3491
|
if (fieldValue) {
|
|
2723
3492
|
for (var j = 0; j < fieldValue.length; j++) {
|
|
2724
|
-
fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j],
|
|
3493
|
+
fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j], 1 + fieldName.length, $scope, fieldName, master, j);
|
|
2725
3494
|
}
|
|
2726
3495
|
}
|
|
2727
3496
|
}
|
|
2728
3497
|
else {
|
|
2729
|
-
if (schemaEntry.
|
|
2730
|
-
setUpInternalLookupLists($scope, schemaEntry.options, schemaEntry.ids, master[schemaEntry.
|
|
3498
|
+
if (schemaEntry.internalRef) {
|
|
3499
|
+
setUpInternalLookupLists($scope, schemaEntry.options, schemaEntry.ids, master[schemaEntry.internalRef.property], schemaEntry.internalRef.value);
|
|
2731
3500
|
}
|
|
2732
3501
|
// Convert {array:['item 1']} to {array:[{x:'item 1'}]}
|
|
2733
|
-
var thisField = getListData(
|
|
2734
|
-
if (schemaEntry.array &&
|
|
3502
|
+
var thisField = getListData(anObject, fieldName, null, $scope);
|
|
3503
|
+
if (schemaEntry.array &&
|
|
3504
|
+
simpleArrayNeedsX(schemaEntry) &&
|
|
3505
|
+
thisField &&
|
|
3506
|
+
!(thisField.length > 0 && thisField[0].x) // Don't keep on coverting
|
|
3507
|
+
) {
|
|
2735
3508
|
for (var k = 0; k < thisField.length; k++) {
|
|
2736
3509
|
thisField[k] = { x: thisField[k] };
|
|
2737
3510
|
}
|
|
2738
3511
|
}
|
|
2739
3512
|
// Convert {lookup:'012abcde'} to {lookup:'List description for 012abcde'}
|
|
2740
|
-
var idList = $scope[suffixCleanId(schemaEntry,
|
|
3513
|
+
var idList = $scope[suffixCleanId(schemaEntry, "_ids")];
|
|
2741
3514
|
var thisConversion = void 0;
|
|
2742
3515
|
if (fieldValue && idList && idList.length > 0) {
|
|
2743
|
-
if (
|
|
2744
|
-
|
|
2745
|
-
|
|
2746
|
-
|
|
3516
|
+
if (
|
|
3517
|
+
// it's not a nested field
|
|
3518
|
+
!fieldName.includes(".") &&
|
|
3519
|
+
// Check we are starting with an ObjectId (ie not being called because of $watch on conversion, with a converted value, which would cause an exception)
|
|
3520
|
+
fieldValue.toString().match(/^[a-f0-9]{24}$/) &&
|
|
3521
|
+
// We are not suppressing conversions
|
|
3522
|
+
(!schemaEntry.internalRef || !schemaEntry.internalRef.noConvert)) {
|
|
3523
|
+
anObject[fieldName] = convertForeignKeys(schemaEntry, fieldValue, $scope[suffixCleanId(schemaEntry, "Options")], idList);
|
|
3524
|
+
}
|
|
2747
3525
|
}
|
|
2748
3526
|
else if (schemaEntry.select2) {
|
|
2749
3527
|
// Do nothing with these - handled elsewhere (and deprecated)
|
|
2750
|
-
console.log(
|
|
3528
|
+
console.log("fng-select2 is deprecated - use fng-ui-select instead");
|
|
2751
3529
|
void (schemaEntry.select2);
|
|
2752
3530
|
}
|
|
2753
3531
|
else if (fieldValue && (thisConversion = getConversionObject($scope, fieldName, schemaName)) &&
|
|
2754
3532
|
thisConversion.fngajax &&
|
|
3533
|
+
typeof thisConversion.fngajax === "function" && // if the field is securely hidden, the directive won't have been invoked at all and therefore the conversion will not have been initialised. but if it's hidden, we don't need to do the conversion anyway
|
|
2755
3534
|
!thisConversion.noconvert) {
|
|
2756
3535
|
thisConversion.fngajax(fieldValue, schemaEntry, function (updateEntry, value) {
|
|
2757
3536
|
// Update the master and (preserving pristine if appropriate) the record
|
|
2758
3537
|
setData(master, updateEntry.name, offset, value);
|
|
2759
|
-
preservePristine(angular.element(
|
|
3538
|
+
preservePristine(angular.element("#" + updateEntry.id), function () {
|
|
2760
3539
|
setData($scope.record, updateEntry.name, offset, value);
|
|
2761
3540
|
});
|
|
2762
3541
|
});
|
|
@@ -2769,9 +3548,9 @@ var fng;
|
|
|
2769
3548
|
// Called when the model is read and when the lookups are read
|
|
2770
3549
|
// No support for nested schemas here as it is called from convertToAngularModel which does that
|
|
2771
3550
|
function convertForeignKeys(schemaElement, input, values, ids) {
|
|
2772
|
-
if (schemaElement.array) {
|
|
3551
|
+
if (schemaElement.array || angular.isArray(input)) {
|
|
2773
3552
|
var returnArray = [];
|
|
2774
|
-
var needsX = !schemaElement.directive || simpleArrayNeedsX(schemaElement);
|
|
3553
|
+
var needsX = schemaElement.array && (!schemaElement.directive || simpleArrayNeedsX(schemaElement));
|
|
2775
3554
|
for (var j = 0; j < input.length; j++) {
|
|
2776
3555
|
var val = input[j];
|
|
2777
3556
|
if (val && val.x) {
|
|
@@ -2815,7 +3594,7 @@ var fng;
|
|
|
2815
3594
|
else {
|
|
2816
3595
|
var index = valuesArray.indexOf(textToConvert);
|
|
2817
3596
|
if (index === -1) {
|
|
2818
|
-
throw new Error(
|
|
3597
|
+
throw new Error("convertListValueToId: Invalid data - value " + textToConvert + " not found in " + valuesArray + " processing " + fname);
|
|
2819
3598
|
}
|
|
2820
3599
|
return idsArray[index];
|
|
2821
3600
|
}
|
|
@@ -2823,7 +3602,7 @@ var fng;
|
|
|
2823
3602
|
var preservePristine = function preservePristine(element, fn) {
|
|
2824
3603
|
// stop the form being set to dirty when a fn is called
|
|
2825
3604
|
// Use when the record (and master) need to be updated by lookup values displayed asynchronously
|
|
2826
|
-
var modelController = element.inheritedData(
|
|
3605
|
+
var modelController = element.inheritedData("$ngModelController");
|
|
2827
3606
|
var isClean = (modelController && modelController.$pristine);
|
|
2828
3607
|
if (isClean) {
|
|
2829
3608
|
// fake it to dirty here and reset after call to fn
|
|
@@ -2835,18 +3614,21 @@ var fng;
|
|
|
2835
3614
|
}
|
|
2836
3615
|
};
|
|
2837
3616
|
var convertIdToListValue = function convertIdToListValue(id, idsArray, valuesArray, fname) {
|
|
2838
|
-
if (typeof (id) ===
|
|
3617
|
+
if (typeof (id) === "object") {
|
|
2839
3618
|
id = id.id;
|
|
2840
3619
|
}
|
|
2841
3620
|
var index = idsArray.indexOf(id);
|
|
2842
3621
|
if (index === -1) {
|
|
2843
|
-
|
|
3622
|
+
index = valuesArray.indexOf(id); // This can get called twice - second time with converted value (not sure how atm) so protect against that...
|
|
3623
|
+
if (index === -1) {
|
|
3624
|
+
throw new Error("convertIdToListValue: Invalid data - id " + id + " not found in " + idsArray + " processing " + fname);
|
|
3625
|
+
}
|
|
2844
3626
|
}
|
|
2845
3627
|
return valuesArray[index];
|
|
2846
3628
|
};
|
|
2847
3629
|
var processServerData = function processServerData(recordFromServer, $scope, ctrlState) {
|
|
2848
3630
|
ctrlState.master = convertToAngularModel($scope.formSchema, recordFromServer, 0, $scope);
|
|
2849
|
-
$scope.phase =
|
|
3631
|
+
$scope.phase = "ready";
|
|
2850
3632
|
$scope.cancel();
|
|
2851
3633
|
};
|
|
2852
3634
|
function convertOldToNew(ref, val, attrib, newVals, oldVals) {
|
|
@@ -2865,7 +3647,7 @@ var fng;
|
|
|
2865
3647
|
var listOnly = (!$scope.id && !$scope.newRecord);
|
|
2866
3648
|
// passing null for formSchema parameter prevents all the work being done when we are just after the list data,
|
|
2867
3649
|
// but should be removed when/if formschemas are cached
|
|
2868
|
-
formGeneratorInstance.handleSchema(
|
|
3650
|
+
formGeneratorInstance.handleSchema("Main " + $scope.modelName, schema, listOnly ? null : $scope.formSchema, $scope.listSchema, "", true, $scope, ctrlState);
|
|
2869
3651
|
function processLookupHandlers(newValue, oldValue) {
|
|
2870
3652
|
// If we have any internal lookups then update the references
|
|
2871
3653
|
$scope.internalLookups.forEach(function (lkp) {
|
|
@@ -2901,18 +3683,36 @@ var fng;
|
|
|
2901
3683
|
}
|
|
2902
3684
|
return retVal;
|
|
2903
3685
|
}
|
|
3686
|
+
function blankListLookup(inst) {
|
|
3687
|
+
setData($scope.record, inst.name);
|
|
3688
|
+
}
|
|
2904
3689
|
var idString = lkp.ref.id.slice(1);
|
|
2905
3690
|
if (idString.includes(".")) {
|
|
2906
|
-
throw new Error("No support for nested list lookups yet - "
|
|
3691
|
+
throw new Error("No support for nested list lookups yet - ".concat(JSON.stringify(lkp.ref)));
|
|
2907
3692
|
}
|
|
2908
3693
|
var newVal = extractIdVal(newValue, idString);
|
|
2909
3694
|
var oldVal = extractIdVal(oldValue, idString);
|
|
2910
3695
|
if (newVal !== oldVal) {
|
|
2911
3696
|
if (newVal) {
|
|
3697
|
+
if (oldVal) {
|
|
3698
|
+
lkp.handlers.forEach(function (h) {
|
|
3699
|
+
h.oldValue = getData($scope.record, h.formInstructions.name);
|
|
3700
|
+
if (angular.isArray(h.oldValue)) {
|
|
3701
|
+
h.oldId = h.oldValue.map(function (a) {
|
|
3702
|
+
return $scope[h.formInstructions.ids][$scope[h.formInstructions.options].indexOf(a)];
|
|
3703
|
+
});
|
|
3704
|
+
}
|
|
3705
|
+
else {
|
|
3706
|
+
h.oldId = $scope[h.formInstructions.ids][$scope[h.formInstructions.options].indexOf(h.oldValue)];
|
|
3707
|
+
}
|
|
3708
|
+
});
|
|
3709
|
+
}
|
|
2912
3710
|
SubmissionsService.readRecord(lkp.ref.collection, newVal).then(function (response) {
|
|
2913
3711
|
lkp.handlers.forEach(function (h) {
|
|
2914
3712
|
var optionsList = $scope[h.formInstructions.options];
|
|
3713
|
+
optionsList.length = 0;
|
|
2915
3714
|
var idList = $scope[h.formInstructions.ids];
|
|
3715
|
+
idList.length = 0;
|
|
2916
3716
|
var data = response.data[lkp.ref.property] || [];
|
|
2917
3717
|
for (var i = 0; i < data.length; i++) {
|
|
2918
3718
|
var option = data[i][lkp.ref.value];
|
|
@@ -2925,7 +3725,33 @@ var fng;
|
|
|
2925
3725
|
optionsList.splice(pos, 0, option);
|
|
2926
3726
|
idList.splice(pos, 0, data[i]._id);
|
|
2927
3727
|
}
|
|
2928
|
-
|
|
3728
|
+
if (Object.keys(oldValue).length === 0) {
|
|
3729
|
+
// Not sure how safe this is, but the record is fresh so I think it's OK...
|
|
3730
|
+
updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState, true);
|
|
3731
|
+
}
|
|
3732
|
+
else if (h.oldId) {
|
|
3733
|
+
// Here we are reacting to a change in the lookup pointer in the record.
|
|
3734
|
+
// If the old id exists in the new idList we can keep it, otherwise we need to blank it.
|
|
3735
|
+
// We need to remember that we can have an array of ids
|
|
3736
|
+
if (angular.isArray(h.oldId)) {
|
|
3737
|
+
h.oldId.forEach(function (id, idx) {
|
|
3738
|
+
var pos = idList.indexOf(id);
|
|
3739
|
+
setData($scope.record, h.formInstructions.name, idx, pos === -1 ? undefined : optionsList[pos]);
|
|
3740
|
+
});
|
|
3741
|
+
}
|
|
3742
|
+
else {
|
|
3743
|
+
var pos_1 = idList.indexOf(h.oldId);
|
|
3744
|
+
if (pos_1 !== -1) {
|
|
3745
|
+
setData($scope.record, h.formInstructions.name, undefined, optionsList[pos_1]);
|
|
3746
|
+
}
|
|
3747
|
+
else {
|
|
3748
|
+
blankListLookup(h.formInstructions);
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3751
|
+
}
|
|
3752
|
+
else {
|
|
3753
|
+
blankListLookup(h.formInstructions);
|
|
3754
|
+
}
|
|
2929
3755
|
});
|
|
2930
3756
|
});
|
|
2931
3757
|
}
|
|
@@ -2933,25 +3759,30 @@ var fng;
|
|
|
2933
3759
|
lkp.handlers.forEach(function (h) {
|
|
2934
3760
|
$scope[h.formInstructions.options].length = 0;
|
|
2935
3761
|
$scope[h.formInstructions.ids].length = 0;
|
|
2936
|
-
|
|
3762
|
+
blankListLookup(h.formInstructions);
|
|
2937
3763
|
});
|
|
2938
3764
|
}
|
|
2939
3765
|
}
|
|
2940
3766
|
});
|
|
2941
3767
|
}
|
|
3768
|
+
function notifyReady() {
|
|
3769
|
+
$scope.phase = "ready";
|
|
3770
|
+
$scope.cancel();
|
|
3771
|
+
processLookupHandlers($scope.record, {});
|
|
3772
|
+
}
|
|
2942
3773
|
if (listOnly) {
|
|
2943
3774
|
ctrlState.allowLocationChange = true;
|
|
2944
3775
|
}
|
|
2945
3776
|
else {
|
|
2946
3777
|
var force = true;
|
|
2947
3778
|
if (!$scope.newRecord) {
|
|
2948
|
-
$scope.dropConversionWatcher = $scope.$watchCollection(
|
|
3779
|
+
$scope.dropConversionWatcher = $scope.$watchCollection("conversions", function (newValue, oldValue) {
|
|
2949
3780
|
if (newValue !== oldValue && $scope.originalData) {
|
|
2950
3781
|
processServerData($scope.originalData, $scope, ctrlState);
|
|
2951
3782
|
}
|
|
2952
3783
|
});
|
|
2953
3784
|
}
|
|
2954
|
-
$scope.$watch(
|
|
3785
|
+
$scope.$watch("record", function (newValue, oldValue) {
|
|
2955
3786
|
if (newValue !== oldValue) {
|
|
2956
3787
|
if (Object.keys(oldValue).length > 0 && $scope.dropConversionWatcher) {
|
|
2957
3788
|
$scope.dropConversionWatcher(); // Don't want to convert changed data
|
|
@@ -2959,11 +3790,27 @@ var fng;
|
|
|
2959
3790
|
}
|
|
2960
3791
|
force = formGeneratorInstance.updateDataDependentDisplay(newValue, oldValue, force, $scope);
|
|
2961
3792
|
processLookupHandlers(newValue, oldValue);
|
|
3793
|
+
if (fng.formsAngular.title) {
|
|
3794
|
+
var title = fng.formsAngular.title.prefix || '';
|
|
3795
|
+
if ($scope['editFormHeader']) {
|
|
3796
|
+
title += $scope['editFormHeader']();
|
|
3797
|
+
}
|
|
3798
|
+
else {
|
|
3799
|
+
for (var listElm in $scope.listSchema) {
|
|
3800
|
+
if ($scope.listSchema.hasOwnProperty(listElm)) {
|
|
3801
|
+
title += $scope.getListData($scope.record, $scope.listSchema[listElm].name) + ' ';
|
|
3802
|
+
}
|
|
3803
|
+
}
|
|
3804
|
+
}
|
|
3805
|
+
title = title.trimEnd() + (fng.formsAngular.title.suffix || '');
|
|
3806
|
+
$window.document.title = title.replace(/<\/?[^>]+(>|$)/g, "");
|
|
3807
|
+
;
|
|
3808
|
+
}
|
|
2962
3809
|
}
|
|
2963
3810
|
}, true);
|
|
2964
3811
|
if ($scope.id) {
|
|
2965
3812
|
// Going to read a record
|
|
2966
|
-
if (typeof $scope.dataEventFunctions.onBeforeRead ===
|
|
3813
|
+
if (typeof $scope.dataEventFunctions.onBeforeRead === "function") {
|
|
2967
3814
|
$scope.dataEventFunctions.onBeforeRead($scope.id, function (err) {
|
|
2968
3815
|
if (err) {
|
|
2969
3816
|
$scope.showError(err);
|
|
@@ -2979,100 +3826,130 @@ var fng;
|
|
|
2979
3826
|
}
|
|
2980
3827
|
else {
|
|
2981
3828
|
// New record
|
|
2982
|
-
ctrlState.
|
|
3829
|
+
ctrlState.allowLocationChange = false;
|
|
3830
|
+
ctrlState.master = $scope.setDefaults($scope.formSchema);
|
|
2983
3831
|
var passedRecord = $scope.initialiseNewRecord || $location.$$search.r;
|
|
2984
3832
|
if (passedRecord) {
|
|
2985
3833
|
try {
|
|
2986
|
-
ctrlState.master
|
|
2987
|
-
|
|
2988
|
-
|
|
2989
|
-
|
|
2990
|
-
|
|
2991
|
-
$scope[$scope.topLevelFormName]
|
|
2992
|
-
|
|
2993
|
-
|
|
2994
|
-
|
|
3834
|
+
Object.assign(ctrlState.master, JSON.parse(passedRecord));
|
|
3835
|
+
if (!$scope["newRecordsStartPristine"]) {
|
|
3836
|
+
// Although this is a new record we are making it dirty from the url so we need to $setDirty
|
|
3837
|
+
$scope.$on("fngCancel", function () {
|
|
3838
|
+
$timeout(function () {
|
|
3839
|
+
if ($scope[$scope.topLevelFormName]) {
|
|
3840
|
+
$scope[$scope.topLevelFormName].$setDirty();
|
|
3841
|
+
}
|
|
3842
|
+
}, 1000); // Has to fire after the setPristime timeout.
|
|
3843
|
+
});
|
|
3844
|
+
}
|
|
2995
3845
|
}
|
|
2996
3846
|
catch (e) {
|
|
2997
|
-
console.log(
|
|
3847
|
+
console.log("Error parsing specified record : " + e.message);
|
|
2998
3848
|
}
|
|
2999
3849
|
}
|
|
3000
|
-
if (typeof $scope.dataEventFunctions.onInitialiseNewRecord ===
|
|
3850
|
+
if (typeof $scope.dataEventFunctions.onInitialiseNewRecord === "function") {
|
|
3851
|
+
console.log("onInitialiseNewRecord is deprecated - use the async version - onNewRecordInit(data,cb)");
|
|
3001
3852
|
$scope.dataEventFunctions.onInitialiseNewRecord(ctrlState.master);
|
|
3002
3853
|
}
|
|
3003
|
-
$scope.
|
|
3004
|
-
|
|
3005
|
-
|
|
3854
|
+
if (typeof $scope.dataEventFunctions.onNewRecordInit === "function") {
|
|
3855
|
+
$scope.dataEventFunctions.onNewRecordInit(ctrlState.master, function (err) {
|
|
3856
|
+
if (err) {
|
|
3857
|
+
$scope.showError(err);
|
|
3858
|
+
}
|
|
3859
|
+
else {
|
|
3860
|
+
notifyReady();
|
|
3861
|
+
}
|
|
3862
|
+
});
|
|
3863
|
+
}
|
|
3864
|
+
else {
|
|
3865
|
+
notifyReady();
|
|
3866
|
+
}
|
|
3006
3867
|
}
|
|
3007
3868
|
}
|
|
3008
3869
|
}
|
|
3009
3870
|
function handleError($scope) {
|
|
3010
3871
|
return function (response) {
|
|
3011
|
-
if ([200, 400].indexOf(response.status) !== -1) {
|
|
3012
|
-
var errorMessage =
|
|
3013
|
-
|
|
3014
|
-
|
|
3015
|
-
|
|
3016
|
-
|
|
3017
|
-
|
|
3018
|
-
|
|
3019
|
-
|
|
3020
|
-
|
|
3021
|
-
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3872
|
+
if ([200, 400, 403].indexOf(response.status) !== -1) {
|
|
3873
|
+
var errorMessage = "";
|
|
3874
|
+
if (response.data && response.data.errors) {
|
|
3875
|
+
for (var errorField in response.data.errors) {
|
|
3876
|
+
if (response.data.errors.hasOwnProperty(errorField)) {
|
|
3877
|
+
errorMessage += "<li><b>" + $filter("titleCase")(errorField) + ": </b> ";
|
|
3878
|
+
switch (response.data.errors[errorField].type) {
|
|
3879
|
+
case "enum":
|
|
3880
|
+
errorMessage += "You need to select from the list of values";
|
|
3881
|
+
break;
|
|
3882
|
+
default:
|
|
3883
|
+
errorMessage += response.data.errors[errorField].message;
|
|
3884
|
+
break;
|
|
3885
|
+
}
|
|
3886
|
+
errorMessage += "</li>";
|
|
3887
|
+
}
|
|
3025
3888
|
}
|
|
3026
3889
|
}
|
|
3027
3890
|
if (errorMessage.length > 0) {
|
|
3028
|
-
errorMessage = response.data.message +
|
|
3891
|
+
errorMessage = (response.data.message || response.data._message) + "<br /><ul>" + errorMessage + "</ul>";
|
|
3029
3892
|
}
|
|
3030
3893
|
else {
|
|
3031
|
-
errorMessage = response.data.message ||
|
|
3894
|
+
errorMessage = response.data.message || response.data._message || response.data.err || "Error! Sorry - No further details available.";
|
|
3032
3895
|
}
|
|
3896
|
+
// anyone using a watch on $scope.phase, and waiting for it to become "ready" before proceeding, will probably
|
|
3897
|
+
// want to know that an error has occurred. This value is NOT used anywhere in forms-angular.
|
|
3898
|
+
$scope.phase = "error";
|
|
3033
3899
|
$scope.showError(errorMessage);
|
|
3034
3900
|
}
|
|
3035
3901
|
else {
|
|
3036
|
-
$scope.showError(response.status +
|
|
3902
|
+
$scope.showError(response.status + " " + JSON.stringify(response.data));
|
|
3037
3903
|
}
|
|
3038
3904
|
};
|
|
3039
3905
|
}
|
|
3040
3906
|
function handleIncomingData(data, $scope, ctrlState) {
|
|
3041
3907
|
ctrlState.allowLocationChange = false;
|
|
3042
|
-
$scope.phase =
|
|
3043
|
-
if (typeof $scope.dataEventFunctions.onAfterRead ===
|
|
3908
|
+
$scope.phase = "reading";
|
|
3909
|
+
if (typeof $scope.dataEventFunctions.onAfterRead === "function") {
|
|
3044
3910
|
$scope.dataEventFunctions.onAfterRead(data);
|
|
3045
3911
|
}
|
|
3046
3912
|
$scope.originalData = data;
|
|
3047
3913
|
processServerData(data, $scope, ctrlState);
|
|
3048
3914
|
}
|
|
3915
|
+
function addArrayLookupToLookupList($scope, formInstructions, ref, lookups) {
|
|
3916
|
+
var nameElements = formInstructions.name.split(".");
|
|
3917
|
+
var refHandler = lookups.find(function (lkp) {
|
|
3918
|
+
return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
|
|
3919
|
+
});
|
|
3920
|
+
var thisHandler = {
|
|
3921
|
+
formInstructions: formInstructions,
|
|
3922
|
+
lastPart: nameElements.pop(),
|
|
3923
|
+
possibleArray: nameElements.join(".")
|
|
3924
|
+
};
|
|
3925
|
+
if (!refHandler) {
|
|
3926
|
+
refHandler = {
|
|
3927
|
+
ref: ref,
|
|
3928
|
+
lookupOptions: [],
|
|
3929
|
+
lookupIds: [],
|
|
3930
|
+
handlers: []
|
|
3931
|
+
};
|
|
3932
|
+
lookups.push(refHandler);
|
|
3933
|
+
}
|
|
3934
|
+
refHandler.handlers.push(thisHandler);
|
|
3935
|
+
$scope[formInstructions.options] = refHandler.lookupOptions;
|
|
3936
|
+
$scope[formInstructions.ids] = refHandler.lookupIds;
|
|
3937
|
+
}
|
|
3049
3938
|
return {
|
|
3050
3939
|
readRecord: function readRecord($scope, ctrlState) {
|
|
3051
|
-
|
|
3052
|
-
|
|
3940
|
+
$scope.readingRecord = SubmissionsService.readRecord($scope.modelName, $scope.id, $scope.formName);
|
|
3941
|
+
$scope.readingRecord
|
|
3053
3942
|
.then(function (response) {
|
|
3054
|
-
var data = response.data;
|
|
3055
|
-
|
|
3056
|
-
|
|
3057
|
-
|
|
3058
|
-
|
|
3059
|
-
//
|
|
3060
|
-
// ctrlState.allowLocationChange = false;
|
|
3061
|
-
// $scope.phase = 'ready';
|
|
3062
|
-
// $scope.record = angular.copy(response.data);
|
|
3063
|
-
// ctrlState.master = angular.copy(response.master);
|
|
3064
|
-
// if (response.changed) {
|
|
3065
|
-
// $timeout(() => {
|
|
3066
|
-
// $scope[$scope.topLevelFormName].$setDirty();
|
|
3067
|
-
// });
|
|
3068
|
-
// } else {
|
|
3069
|
-
// $timeout($scope.setPristine);
|
|
3070
|
-
// }
|
|
3943
|
+
var data = angular.copy(response.data);
|
|
3944
|
+
handleIncomingData(data, $scope, ctrlState);
|
|
3945
|
+
}, function (error) {
|
|
3946
|
+
if (error.status === 404) {
|
|
3947
|
+
$location.path("/404");
|
|
3071
3948
|
}
|
|
3072
3949
|
else {
|
|
3073
|
-
|
|
3950
|
+
$scope.handleHttpError(error);
|
|
3074
3951
|
}
|
|
3075
|
-
}
|
|
3952
|
+
});
|
|
3076
3953
|
},
|
|
3077
3954
|
scrollTheList: function scrollTheList($scope) {
|
|
3078
3955
|
var pagesLoaded = $scope.pagesLoaded;
|
|
@@ -3081,42 +3958,64 @@ var fng;
|
|
|
3081
3958
|
find: $location.$$search.f,
|
|
3082
3959
|
limit: $scope.pageSize,
|
|
3083
3960
|
skip: pagesLoaded * $scope.pageSize,
|
|
3084
|
-
order: $location.$$search.o
|
|
3961
|
+
order: $location.$$search.o,
|
|
3962
|
+
concatenate: false
|
|
3085
3963
|
})
|
|
3086
3964
|
.then(function (response) {
|
|
3087
3965
|
var data = response.data;
|
|
3088
3966
|
if (angular.isArray(data)) {
|
|
3089
|
-
//
|
|
3967
|
+
// if the options for the resource identified by $scope.modelName has disambiguation parameters,
|
|
3968
|
+
// and that resource has more than one list field, the items returned by getPagedAndFilteredList
|
|
3969
|
+
// might include a "disambiguation" property. for this to appear on the list page, we need
|
|
3970
|
+
// to add an item for it to the list schema
|
|
3971
|
+
if (!$scope.listSchema.find(function (f) { return f.name === "disambiguation"; }) && data.some(function (d) { return d.disambiguation; })) {
|
|
3972
|
+
$scope.listSchema.push({
|
|
3973
|
+
name: "disambiguation",
|
|
3974
|
+
});
|
|
3975
|
+
}
|
|
3976
|
+
// I have seen an intermittent problem where a page is requested twice
|
|
3090
3977
|
if (pagesLoaded === $scope.pagesLoaded) {
|
|
3091
3978
|
$scope.pagesLoaded++;
|
|
3092
3979
|
$scope.recordList = $scope.recordList.concat(data);
|
|
3093
3980
|
}
|
|
3094
3981
|
else {
|
|
3095
|
-
console.log(
|
|
3982
|
+
console.log("DEBUG: infinite scroll component asked for a page twice - the model was " + $scope.modelName);
|
|
3096
3983
|
}
|
|
3097
3984
|
}
|
|
3098
3985
|
else {
|
|
3099
|
-
$scope.showError(data,
|
|
3986
|
+
$scope.showError(data, "Invalid query");
|
|
3100
3987
|
}
|
|
3101
3988
|
}, $scope.handleHttpError);
|
|
3102
3989
|
},
|
|
3103
|
-
|
|
3104
|
-
|
|
3105
|
-
SubmissionsService.deleteRecord(
|
|
3990
|
+
deleteRecord: function deleteRecord(id, $scope, ctrlState) {
|
|
3991
|
+
$scope.phase = "deleting";
|
|
3992
|
+
SubmissionsService.deleteRecord($scope.modelName, id, $scope.formName)
|
|
3106
3993
|
.then(function () {
|
|
3107
|
-
if (typeof $scope.dataEventFunctions.onAfterDelete ===
|
|
3994
|
+
if (typeof $scope.dataEventFunctions.onAfterDelete === "function") {
|
|
3108
3995
|
$scope.dataEventFunctions.onAfterDelete(ctrlState.master);
|
|
3109
3996
|
}
|
|
3110
|
-
routingService.redirectTo()(
|
|
3997
|
+
routingService.redirectTo()("onDelete", $scope, $location);
|
|
3998
|
+
}, function (err) {
|
|
3999
|
+
var _a;
|
|
4000
|
+
if (err.status === 404) {
|
|
4001
|
+
// Someone already deleted it
|
|
4002
|
+
routingService.redirectTo()("onDelete", $scope, $location);
|
|
4003
|
+
}
|
|
4004
|
+
else if (err.status === 403) {
|
|
4005
|
+
$scope.showError(((_a = err.data) === null || _a === void 0 ? void 0 : _a.message) || err.message || err.data || err, 'Permission denied');
|
|
4006
|
+
}
|
|
4007
|
+
else {
|
|
4008
|
+
$scope.showError("".concat(err.statusText, " (").concat(err.status, ") while deleting record<br />").concat(err.data), 'Error deleting record');
|
|
4009
|
+
}
|
|
3111
4010
|
});
|
|
3112
4011
|
},
|
|
3113
4012
|
updateDocument: function updateDocument(dataToSave, options, $scope, ctrlState) {
|
|
3114
|
-
$scope.phase =
|
|
3115
|
-
SubmissionsService.updateRecord($scope.modelName, $scope.id, dataToSave)
|
|
4013
|
+
$scope.phase = "updating";
|
|
4014
|
+
SubmissionsService.updateRecord($scope.modelName, $scope.id, dataToSave, $scope.formName)
|
|
3116
4015
|
.then(function (response) {
|
|
3117
4016
|
var data = response.data;
|
|
3118
4017
|
if (data.success !== false) {
|
|
3119
|
-
if (typeof $scope.dataEventFunctions.onAfterUpdate ===
|
|
4018
|
+
if (typeof $scope.dataEventFunctions.onAfterUpdate === "function") {
|
|
3120
4019
|
$scope.dataEventFunctions.onAfterUpdate(data, ctrlState.master);
|
|
3121
4020
|
}
|
|
3122
4021
|
if (options.redirect) {
|
|
@@ -3133,21 +4032,24 @@ var fng;
|
|
|
3133
4032
|
else {
|
|
3134
4033
|
$scope.showError(data);
|
|
3135
4034
|
}
|
|
3136
|
-
},
|
|
4035
|
+
}, function (err) {
|
|
4036
|
+
$scope.handleHttpError(err);
|
|
4037
|
+
});
|
|
3137
4038
|
},
|
|
3138
|
-
createNew: function createNew(dataToSave, options, $scope) {
|
|
3139
|
-
SubmissionsService.createRecord($scope.modelName, dataToSave)
|
|
4039
|
+
createNew: function createNew(dataToSave, options, $scope, ctrlState) {
|
|
4040
|
+
SubmissionsService.createRecord($scope.modelName, dataToSave, $scope.formName)
|
|
3140
4041
|
.then(function (response) {
|
|
3141
4042
|
var data = response.data;
|
|
3142
4043
|
if (data.success !== false) {
|
|
3143
|
-
|
|
4044
|
+
ctrlState.allowLocationChange = true;
|
|
4045
|
+
if (typeof $scope.dataEventFunctions.onAfterCreate === "function") {
|
|
3144
4046
|
$scope.dataEventFunctions.onAfterCreate(data);
|
|
3145
4047
|
}
|
|
3146
4048
|
if (options.redirect) {
|
|
3147
4049
|
$window.location = options.redirect;
|
|
3148
4050
|
}
|
|
3149
4051
|
else {
|
|
3150
|
-
routingService.redirectTo()(
|
|
4052
|
+
routingService.redirectTo()("edit", $scope, $location, data._id);
|
|
3151
4053
|
}
|
|
3152
4054
|
}
|
|
3153
4055
|
else {
|
|
@@ -3157,75 +4059,55 @@ var fng;
|
|
|
3157
4059
|
},
|
|
3158
4060
|
getListData: getListData,
|
|
3159
4061
|
suffixCleanId: suffixCleanId,
|
|
4062
|
+
getData: getData,
|
|
3160
4063
|
setData: setData,
|
|
3161
4064
|
setUpLookupOptions: function setUpLookupOptions(lookupCollection, schemaElement, $scope, ctrlState, handleSchema) {
|
|
3162
4065
|
var optionsList = $scope[schemaElement.options] = [];
|
|
3163
4066
|
var idList = $scope[schemaElement.ids] = [];
|
|
3164
|
-
|
|
4067
|
+
var dataRequest = !!schemaElement.filter
|
|
4068
|
+
? SubmissionsService.getPagedAndFilteredList(lookupCollection, Object.assign({ concatenate: true }, schemaElement.filter)) // { concatenate: true } causes it to concatenate the list fields into the .text property of ILookupItem objects
|
|
4069
|
+
: SubmissionsService.getAllListAttributes(lookupCollection);
|
|
4070
|
+
dataRequest
|
|
3165
4071
|
.then(function (response) {
|
|
3166
|
-
var
|
|
3167
|
-
|
|
3168
|
-
|
|
3169
|
-
|
|
3170
|
-
|
|
3171
|
-
|
|
3172
|
-
|
|
3173
|
-
|
|
3174
|
-
|
|
3175
|
-
|
|
3176
|
-
dataRequest
|
|
3177
|
-
.then(function (response) {
|
|
3178
|
-
var data = response.data;
|
|
3179
|
-
if (data) {
|
|
3180
|
-
for (var i = 0; i < data.length; i++) {
|
|
3181
|
-
var option = '';
|
|
3182
|
-
for (var j = 0; j < listInstructions.length; j++) {
|
|
3183
|
-
var thisVal = data[i][listInstructions[j].name];
|
|
3184
|
-
option += thisVal ? thisVal + ' ' : '';
|
|
4072
|
+
var items = response.data;
|
|
4073
|
+
if (items) {
|
|
4074
|
+
items.sort(function (a, b) { return a.text.localeCompare(b.text); });
|
|
4075
|
+
optionsList.push.apply(optionsList, items.map(function (i) { return i.text; }));
|
|
4076
|
+
idList.push.apply(idList, items.map(function (i) { return i.id; }));
|
|
4077
|
+
var dupes = new Set();
|
|
4078
|
+
for (var i = 0; i < optionsList.length - 1; i++) {
|
|
4079
|
+
for (var j = i + 1; j < optionsList.length; j++) {
|
|
4080
|
+
if (_.isEqual(optionsList[i], optionsList[j])) {
|
|
4081
|
+
dupes.add(optionsList[i]);
|
|
3185
4082
|
}
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
|
|
3189
|
-
|
|
3190
|
-
|
|
3191
|
-
|
|
4083
|
+
}
|
|
4084
|
+
}
|
|
4085
|
+
// append the id to any duplicates to make them unique
|
|
4086
|
+
dupes.forEach(function (d) {
|
|
4087
|
+
for (var i = 0; i < optionsList.length; i++) {
|
|
4088
|
+
if (optionsList[i] === d) {
|
|
4089
|
+
optionsList[i] += "(" + idList[i] + ")";
|
|
3192
4090
|
}
|
|
3193
|
-
optionsList.splice(pos, 0, option);
|
|
3194
|
-
idList.splice(pos, 0, data[i]._id);
|
|
3195
4091
|
}
|
|
3196
|
-
|
|
4092
|
+
});
|
|
4093
|
+
if ($scope.readingRecord) {
|
|
4094
|
+
$scope.readingRecord
|
|
4095
|
+
.then(function () {
|
|
4096
|
+
updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
|
|
4097
|
+
});
|
|
3197
4098
|
}
|
|
3198
|
-
}
|
|
4099
|
+
}
|
|
4100
|
+
})
|
|
4101
|
+
.catch(function (e) {
|
|
4102
|
+
$scope.handleHttpError(e);
|
|
3199
4103
|
});
|
|
3200
4104
|
},
|
|
3201
4105
|
setUpLookupListOptions: function setUpLookupListOptions(ref, formInstructions, $scope, ctrlState) {
|
|
3202
4106
|
var optionsList = $scope[formInstructions.options] = [];
|
|
3203
4107
|
var idList = $scope[formInstructions.ids] = [];
|
|
3204
|
-
if (ref.id[0] ===
|
|
3205
|
-
// id of document
|
|
3206
|
-
|
|
3207
|
-
var nameElements = formInstructions.name.split('.');
|
|
3208
|
-
var refHandler = $scope.listLookups.find(function (lkp) {
|
|
3209
|
-
return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
|
|
3210
|
-
});
|
|
3211
|
-
var thisHandler = {
|
|
3212
|
-
formInstructions: formInstructions,
|
|
3213
|
-
lastPart: nameElements.pop(),
|
|
3214
|
-
possibleArray: nameElements.join('.')
|
|
3215
|
-
};
|
|
3216
|
-
if (!refHandler) {
|
|
3217
|
-
refHandler = {
|
|
3218
|
-
ref: ref,
|
|
3219
|
-
lookupOptions: [],
|
|
3220
|
-
lookupIds: [],
|
|
3221
|
-
handlers: []
|
|
3222
|
-
};
|
|
3223
|
-
$scope.listLookups.push(refHandler);
|
|
3224
|
-
}
|
|
3225
|
-
refHandler.handlers.push(thisHandler);
|
|
3226
|
-
$scope[formInstructions.options] = refHandler.lookupOptions;
|
|
3227
|
-
$scope[formInstructions.ids] = refHandler.lookupIds;
|
|
3228
|
-
// TODO DRY this and handleInternalLookup below
|
|
4108
|
+
if (ref.id[0] === "$") {
|
|
4109
|
+
// id of document that contains out lookup list comes from record, so we need to deal with in $watch by adding it to listLookups
|
|
4110
|
+
addArrayLookupToLookupList($scope, formInstructions, ref, $scope.listLookups);
|
|
3229
4111
|
}
|
|
3230
4112
|
else {
|
|
3231
4113
|
// we can do it now
|
|
@@ -3236,7 +4118,7 @@ var fng;
|
|
|
3236
4118
|
var pos = _.sortedIndex(optionsList, option);
|
|
3237
4119
|
// handle dupes
|
|
3238
4120
|
if (optionsList[pos] === option) {
|
|
3239
|
-
option = option +
|
|
4121
|
+
option = option + " (" + data[i]._id + ")";
|
|
3240
4122
|
pos = _.sortedIndex(optionsList, option);
|
|
3241
4123
|
}
|
|
3242
4124
|
optionsList.splice(pos, 0, option);
|
|
@@ -3247,27 +4129,7 @@ var fng;
|
|
|
3247
4129
|
}
|
|
3248
4130
|
},
|
|
3249
4131
|
handleInternalLookup: function handleInternalLookup($scope, formInstructions, ref) {
|
|
3250
|
-
|
|
3251
|
-
var refHandler = $scope.internalLookups.find(function (lkp) {
|
|
3252
|
-
return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
|
|
3253
|
-
});
|
|
3254
|
-
var thisHandler = {
|
|
3255
|
-
formInstructions: formInstructions,
|
|
3256
|
-
lastPart: nameElements.pop(),
|
|
3257
|
-
possibleArray: nameElements.join('.')
|
|
3258
|
-
};
|
|
3259
|
-
if (!refHandler) {
|
|
3260
|
-
refHandler = {
|
|
3261
|
-
ref: ref,
|
|
3262
|
-
lookupOptions: [],
|
|
3263
|
-
lookupIds: [],
|
|
3264
|
-
handlers: []
|
|
3265
|
-
};
|
|
3266
|
-
$scope.internalLookups.push(refHandler);
|
|
3267
|
-
}
|
|
3268
|
-
refHandler.handlers.push(thisHandler);
|
|
3269
|
-
$scope[formInstructions.options] = refHandler.lookupOptions;
|
|
3270
|
-
$scope[formInstructions.ids] = refHandler.lookupIds;
|
|
4132
|
+
addArrayLookupToLookupList($scope, formInstructions, ref, $scope.internalLookups);
|
|
3271
4133
|
},
|
|
3272
4134
|
preservePristine: preservePristine,
|
|
3273
4135
|
// Reverse the process of convertToAngularModel
|
|
@@ -3284,48 +4146,54 @@ var fng;
|
|
|
3284
4146
|
}
|
|
3285
4147
|
return retVal;
|
|
3286
4148
|
}
|
|
3287
|
-
|
|
3288
|
-
var
|
|
3289
|
-
var
|
|
3290
|
-
|
|
4149
|
+
var _loop_1 = function () {
|
|
4150
|
+
var schemaI = schema[i];
|
|
4151
|
+
var fieldname = schemaI.name.slice(prefixLength);
|
|
4152
|
+
var thisField = getListData(anObject, fieldname, null, $scope);
|
|
4153
|
+
if (schemaI.schema) {
|
|
3291
4154
|
if (thisField) {
|
|
3292
4155
|
for (var j = 0; j < thisField.length; j++) {
|
|
3293
|
-
thisField[j] = convertToMongoModel(
|
|
4156
|
+
thisField[j] = convertToMongoModel(schemaI.schema, thisField[j], 1 + fieldname.length, $scope, fieldname);
|
|
3294
4157
|
}
|
|
3295
4158
|
}
|
|
3296
4159
|
}
|
|
3297
4160
|
else {
|
|
3298
4161
|
// Convert {array:[{x:'item 1'}]} to {array:['item 1']}
|
|
3299
|
-
if (
|
|
4162
|
+
if (schemaI.array && simpleArrayNeedsX(schemaI) && thisField) {
|
|
3300
4163
|
for (var k = 0; k < thisField.length; k++) {
|
|
3301
4164
|
thisField[k] = thisField[k].x;
|
|
3302
4165
|
}
|
|
3303
4166
|
}
|
|
3304
4167
|
// Convert {lookup:'List description for 012abcde'} to {lookup:'012abcde'}
|
|
3305
|
-
var
|
|
3306
|
-
|
|
3307
|
-
if (idList && idList.length > 0) {
|
|
4168
|
+
var idList_1 = $scope[suffixCleanId(schemaI, "_ids")];
|
|
4169
|
+
if (idList_1 && idList_1.length > 0) {
|
|
3308
4170
|
updateObject(fieldname, anObject, function (value) {
|
|
3309
|
-
return convertToForeignKeys(
|
|
4171
|
+
return convertToForeignKeys(schemaI, value, $scope[suffixCleanId(schemaI, "Options")], idList_1);
|
|
3310
4172
|
});
|
|
3311
4173
|
}
|
|
3312
|
-
else
|
|
3313
|
-
var
|
|
3314
|
-
|
|
3315
|
-
|
|
3316
|
-
newVal =
|
|
3317
|
-
if (
|
|
3318
|
-
|
|
3319
|
-
|
|
4174
|
+
else {
|
|
4175
|
+
var thisConversion = getConversionObject($scope, fieldname, schemaName);
|
|
4176
|
+
if (thisConversion) {
|
|
4177
|
+
var lookup = getData(anObject, fieldname, null);
|
|
4178
|
+
var newVal = void 0;
|
|
4179
|
+
if (schemaI.array) {
|
|
4180
|
+
newVal = [];
|
|
4181
|
+
if (lookup) {
|
|
4182
|
+
for (var n = 0; n < lookup.length; n++) {
|
|
4183
|
+
newVal[n] = convertLookup(lookup[n], thisConversion);
|
|
4184
|
+
}
|
|
3320
4185
|
}
|
|
3321
4186
|
}
|
|
4187
|
+
else {
|
|
4188
|
+
newVal = convertLookup(lookup, thisConversion);
|
|
4189
|
+
}
|
|
4190
|
+
setData(anObject, fieldname, null, newVal);
|
|
3322
4191
|
}
|
|
3323
|
-
else {
|
|
3324
|
-
newVal = convertLookup(lookup, thisConversion);
|
|
3325
|
-
}
|
|
3326
|
-
setData(anObject, fieldname, null, newVal);
|
|
3327
4192
|
}
|
|
3328
4193
|
}
|
|
4194
|
+
};
|
|
4195
|
+
for (var i = 0; i < schema.length; i++) {
|
|
4196
|
+
_loop_1();
|
|
3329
4197
|
}
|
|
3330
4198
|
return anObject;
|
|
3331
4199
|
},
|
|
@@ -3335,7 +4203,7 @@ var fng;
|
|
|
3335
4203
|
$scope.handleHttpError = handleError($scope);
|
|
3336
4204
|
$scope.cancel = function () {
|
|
3337
4205
|
angular.copy(ctrlState.master, $scope.record);
|
|
3338
|
-
$scope.$broadcast(
|
|
4206
|
+
$scope.$broadcast("fngCancel", $scope);
|
|
3339
4207
|
// Let call backs etc resolve in case they dirty form, then clean it
|
|
3340
4208
|
$timeout($scope.setPristine);
|
|
3341
4209
|
};
|
|
@@ -3344,15 +4212,24 @@ var fng;
|
|
|
3344
4212
|
// scope.$emit('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
|
|
3345
4213
|
// or
|
|
3346
4214
|
// scope.$broadcast('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
|
|
3347
|
-
$scope.$on(
|
|
3348
|
-
|
|
4215
|
+
$scope.$on("showErrorMessage", function (event, args) {
|
|
4216
|
+
if (!event.defaultPrevented) {
|
|
4217
|
+
event.defaultPrevented = true;
|
|
4218
|
+
$scope.showError(args.body, args.title);
|
|
4219
|
+
}
|
|
3349
4220
|
});
|
|
3350
4221
|
$scope.showError = function (error, alertTitle) {
|
|
3351
|
-
$scope.alertTitle = alertTitle ? alertTitle :
|
|
3352
|
-
|
|
3353
|
-
$scope.
|
|
4222
|
+
$scope.alertTitle = alertTitle ? alertTitle : "Error!";
|
|
4223
|
+
$timeout(function () {
|
|
4224
|
+
$scope.phase = 'ready';
|
|
4225
|
+
}, 25);
|
|
4226
|
+
if (typeof error === "string") {
|
|
4227
|
+
$scope.errorMessage = $sce.trustAsHtml(error);
|
|
4228
|
+
}
|
|
4229
|
+
else if (!error) {
|
|
4230
|
+
$scope.errorMessage = "An error occurred - that's all we got. Sorry.";
|
|
3354
4231
|
}
|
|
3355
|
-
else if (error.message && typeof error.message ===
|
|
4232
|
+
else if (error.message && typeof error.message === "string") {
|
|
3356
4233
|
$scope.errorMessage = error.message;
|
|
3357
4234
|
}
|
|
3358
4235
|
else if (error.data && error.data.message) {
|
|
@@ -3366,16 +4243,35 @@ var fng;
|
|
|
3366
4243
|
$scope.errorMessage = error;
|
|
3367
4244
|
}
|
|
3368
4245
|
}
|
|
4246
|
+
$scope.errorHideTimer = window.setTimeout(function () {
|
|
4247
|
+
$scope.dismissError();
|
|
4248
|
+
$scope.$digest();
|
|
4249
|
+
}, 3500 + (1000 * ($scope.alertTitle + $scope.errorMessage).length / 50));
|
|
4250
|
+
$scope.errorVisible = true;
|
|
4251
|
+
window.setTimeout(function () {
|
|
4252
|
+
$scope.$digest();
|
|
4253
|
+
});
|
|
4254
|
+
};
|
|
4255
|
+
$scope.clearTimeout = function () {
|
|
4256
|
+
if ($scope.errorHideTimer) {
|
|
4257
|
+
clearTimeout($scope.errorHideTimer);
|
|
4258
|
+
delete $scope.errorHideTimer;
|
|
4259
|
+
}
|
|
3369
4260
|
};
|
|
3370
4261
|
$scope.dismissError = function () {
|
|
4262
|
+
$scope.clearTimeout;
|
|
4263
|
+
$scope.errorVisible = false;
|
|
3371
4264
|
delete $scope.errorMessage;
|
|
3372
4265
|
delete $scope.alertTitle;
|
|
3373
4266
|
};
|
|
4267
|
+
$scope.stickError = function () {
|
|
4268
|
+
clearTimeout($scope.errorHideTimer);
|
|
4269
|
+
};
|
|
3374
4270
|
$scope.prepareForSave = function (cb) {
|
|
3375
4271
|
//Convert the lookup values into ids
|
|
3376
4272
|
var dataToSave = recordHandlerInstance.convertToMongoModel($scope.formSchema, angular.copy($scope.record), 0, $scope);
|
|
3377
4273
|
if ($scope.id) {
|
|
3378
|
-
if (typeof $scope.dataEventFunctions.onBeforeUpdate ===
|
|
4274
|
+
if (typeof $scope.dataEventFunctions.onBeforeUpdate === "function") {
|
|
3379
4275
|
$scope.dataEventFunctions.onBeforeUpdate(dataToSave, ctrlState.master, function (err) {
|
|
3380
4276
|
if (err) {
|
|
3381
4277
|
cb(err);
|
|
@@ -3390,7 +4286,7 @@ var fng;
|
|
|
3390
4286
|
}
|
|
3391
4287
|
}
|
|
3392
4288
|
else {
|
|
3393
|
-
if (typeof $scope.dataEventFunctions.onBeforeCreate ===
|
|
4289
|
+
if (typeof $scope.dataEventFunctions.onBeforeCreate === "function") {
|
|
3394
4290
|
$scope.dataEventFunctions.onBeforeCreate(dataToSave, function (err) {
|
|
3395
4291
|
if (err) {
|
|
3396
4292
|
cb(err);
|
|
@@ -3409,22 +4305,24 @@ var fng;
|
|
|
3409
4305
|
options = options || {};
|
|
3410
4306
|
$scope.prepareForSave(function (err, dataToSave) {
|
|
3411
4307
|
if (err) {
|
|
3412
|
-
if (err !==
|
|
3413
|
-
$
|
|
4308
|
+
if (err !== "_update_handled_") {
|
|
4309
|
+
$timeout(function () {
|
|
4310
|
+
$scope.showError(err);
|
|
4311
|
+
});
|
|
3414
4312
|
}
|
|
3415
4313
|
}
|
|
3416
4314
|
else if ($scope.id) {
|
|
3417
4315
|
recordHandlerInstance.updateDocument(dataToSave, options, $scope, ctrlState);
|
|
3418
4316
|
}
|
|
3419
4317
|
else {
|
|
3420
|
-
recordHandlerInstance.createNew(dataToSave, options, $scope);
|
|
4318
|
+
recordHandlerInstance.createNew(dataToSave, options, $scope, ctrlState);
|
|
3421
4319
|
}
|
|
3422
4320
|
});
|
|
3423
4321
|
};
|
|
3424
4322
|
$scope.newClick = function () {
|
|
3425
|
-
routingService.redirectTo()(
|
|
4323
|
+
routingService.redirectTo()("new", $scope, $location);
|
|
3426
4324
|
};
|
|
3427
|
-
$scope.$on(
|
|
4325
|
+
$scope.$on("$locationChangeStart", function (event, next) {
|
|
3428
4326
|
// let changed = !$scope.isCancelDisabled();
|
|
3429
4327
|
// let curPath = window.location.href.split('/');
|
|
3430
4328
|
// let nextPath = next.split('/');
|
|
@@ -3443,21 +4341,12 @@ var fng;
|
|
|
3443
4341
|
if (!ctrlState.allowLocationChange && !$scope.isCancelDisabled()) {
|
|
3444
4342
|
event.preventDefault();
|
|
3445
4343
|
var modalInstance = $uibModal.open({
|
|
3446
|
-
template:
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
'<div class="modal-body">' +
|
|
3450
|
-
' <p>Would you like to save your changes?</p>' +
|
|
3451
|
-
'</div>' +
|
|
3452
|
-
'<div class="modal-footer">' +
|
|
3453
|
-
' <button class="btn btn-primary dlg-yes" ng-click="yes()">Yes</button>' +
|
|
3454
|
-
' <button class="btn btn-warning dlg-no" ng-click="no()">No</button>' +
|
|
3455
|
-
' <button class="btn dlg-cancel" ng-click="cancel()">Cancel</button>' +
|
|
3456
|
-
'</div>',
|
|
3457
|
-
controller: 'SaveChangesModalCtrl',
|
|
3458
|
-
backdrop: 'static'
|
|
4344
|
+
template: "<div class=\"modal-header\">\n <h3>Record modified</h3>\n</div>\n<div class=\"modal-body\">\n <p>Would you like to save your changes?</p>\n</div>\n<div class=\"modal-footer\">\n <button class=\"btn btn-primary dlg-yes\" ng-click=\"yes()\">Yes</button>\n <button class=\"btn btn-warning dlg-no\" ng-click=\"no()\">No</button>\n <button class=\"btn dlg-cancel\" ng-click=\"cancel()\">Cancel</button>\n</div>",
|
|
4345
|
+
controller: "SaveChangesModalCtrl",
|
|
4346
|
+
backdrop: "static"
|
|
3459
4347
|
});
|
|
3460
|
-
modalInstance.result
|
|
4348
|
+
modalInstance.result
|
|
4349
|
+
.then(function (result) {
|
|
3461
4350
|
if (result) {
|
|
3462
4351
|
$scope.save({ redirect: next, allowChange: true }); // save changes
|
|
3463
4352
|
}
|
|
@@ -3465,7 +4354,8 @@ var fng;
|
|
|
3465
4354
|
ctrlState.allowLocationChange = true;
|
|
3466
4355
|
$window.location = next;
|
|
3467
4356
|
}
|
|
3468
|
-
})
|
|
4357
|
+
})
|
|
4358
|
+
.catch(_handleCancel);
|
|
3469
4359
|
}
|
|
3470
4360
|
});
|
|
3471
4361
|
$scope.deleteClick = function () {
|
|
@@ -3476,85 +4366,180 @@ var fng;
|
|
|
3476
4366
|
}
|
|
3477
4367
|
else {
|
|
3478
4368
|
var modalInstance = $uibModal.open({
|
|
3479
|
-
template:
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
'<div class="modal-body">' +
|
|
3483
|
-
' <p>Are you sure you want to delete this record?</p>' +
|
|
3484
|
-
'</div>' +
|
|
3485
|
-
'<div class="modal-footer">' +
|
|
3486
|
-
' <button class="btn btn-primary dlg-no" ng-click="cancel()">No</button>' +
|
|
3487
|
-
' <button class="btn btn-warning dlg-yes" ng-click="yes()">Yes</button>' +
|
|
3488
|
-
'</div>',
|
|
3489
|
-
controller: 'SaveChangesModalCtrl',
|
|
3490
|
-
backdrop: 'static'
|
|
4369
|
+
template: "<div class=\"modal-header\">\n <h3>Delete Item</h3>\n</div>\n<div class=\"modal-body\">\n <p>Are you sure you want to delete this record?</p>\n</div>\n<div class=\"modal-footer\">\n <button class=\"btn btn-primary dlg-no\" ng-click=\"cancel()\">No</button>\n <button class=\"btn btn-warning dlg-yes\" ng-click=\"yes()\">Yes</button>\n</div>",
|
|
4370
|
+
controller: "SaveChangesModalCtrl",
|
|
4371
|
+
backdrop: "static"
|
|
3491
4372
|
});
|
|
3492
4373
|
confirmDelete = modalInstance.result;
|
|
3493
4374
|
}
|
|
3494
4375
|
confirmDelete.then(function (result) {
|
|
4376
|
+
function doTheDeletion() {
|
|
4377
|
+
recordHandlerInstance.deleteRecord($scope.id, $scope, ctrlState);
|
|
4378
|
+
}
|
|
3495
4379
|
if (result) {
|
|
3496
|
-
if (typeof $scope.dataEventFunctions.onBeforeDelete ===
|
|
4380
|
+
if (typeof $scope.dataEventFunctions.onBeforeDelete === "function") {
|
|
3497
4381
|
$scope.dataEventFunctions.onBeforeDelete(ctrlState.master, function (err) {
|
|
3498
4382
|
if (err) {
|
|
3499
|
-
if (err !==
|
|
4383
|
+
if (err !== "_delete_handled_") {
|
|
3500
4384
|
$scope.showError(err);
|
|
3501
4385
|
}
|
|
3502
4386
|
}
|
|
3503
4387
|
else {
|
|
3504
|
-
|
|
4388
|
+
doTheDeletion();
|
|
3505
4389
|
}
|
|
3506
4390
|
});
|
|
3507
4391
|
}
|
|
3508
4392
|
else {
|
|
3509
|
-
|
|
4393
|
+
doTheDeletion();
|
|
3510
4394
|
}
|
|
3511
4395
|
}
|
|
3512
|
-
})
|
|
4396
|
+
})
|
|
4397
|
+
.catch(_handleCancel);
|
|
3513
4398
|
}
|
|
3514
4399
|
};
|
|
3515
4400
|
$scope.isCancelDisabled = function () {
|
|
3516
|
-
|
|
4401
|
+
var _a;
|
|
4402
|
+
if (($scope[$scope.topLevelFormName] && $scope[$scope.topLevelFormName].$pristine) || $scope.phase !== "ready") {
|
|
4403
|
+
return true;
|
|
4404
|
+
}
|
|
4405
|
+
else if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isCancelDisabled) === "function") {
|
|
3517
4406
|
return $scope.disableFunctions.isCancelDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
3518
4407
|
}
|
|
3519
4408
|
else {
|
|
3520
|
-
return
|
|
4409
|
+
return false;
|
|
3521
4410
|
}
|
|
3522
4411
|
};
|
|
3523
4412
|
$scope.isSaveDisabled = function () {
|
|
3524
|
-
|
|
3525
|
-
|
|
4413
|
+
var _a;
|
|
4414
|
+
$scope.whyDisabled = undefined;
|
|
4415
|
+
var pristine = false;
|
|
4416
|
+
function generateWhyDisabledMessage(form, subFormName) {
|
|
4417
|
+
form.$$controls.forEach(function (c) {
|
|
4418
|
+
if (c.$invalid) {
|
|
4419
|
+
if (c.$$controls) {
|
|
4420
|
+
// nested form
|
|
4421
|
+
generateWhyDisabledMessage(c, c.$name);
|
|
4422
|
+
}
|
|
4423
|
+
else {
|
|
4424
|
+
$scope.whyDisabled += "<br /><strong>";
|
|
4425
|
+
if (subFormName) {
|
|
4426
|
+
$scope.whyDisabled += subFormName + ' ';
|
|
4427
|
+
}
|
|
4428
|
+
if (cssFrameworkService.framework() === "bs2" &&
|
|
4429
|
+
c.$$element &&
|
|
4430
|
+
c.$$element.parent() &&
|
|
4431
|
+
c.$$element.parent().parent() &&
|
|
4432
|
+
c.$$element.parent().parent().find("label") &&
|
|
4433
|
+
c.$$element.parent().parent().find("label").text()) {
|
|
4434
|
+
$scope.whyDisabled += c.$$element.parent().parent().find("label").text();
|
|
4435
|
+
}
|
|
4436
|
+
else if (cssFrameworkService.framework() === "bs3" &&
|
|
4437
|
+
c.$$element &&
|
|
4438
|
+
c.$$element.parent() &&
|
|
4439
|
+
c.$$element.parent().parent() &&
|
|
4440
|
+
c.$$element.parent().parent().parent() &&
|
|
4441
|
+
c.$$element.parent().parent().parent().find("label") &&
|
|
4442
|
+
c.$$element.parent().parent().parent().find("label").text()) {
|
|
4443
|
+
$scope.whyDisabled += c.$$element.parent().parent().parent().find("label").text();
|
|
4444
|
+
}
|
|
4445
|
+
else {
|
|
4446
|
+
$scope.whyDisabled += c.$name;
|
|
4447
|
+
}
|
|
4448
|
+
$scope.whyDisabled += "</strong>: ";
|
|
4449
|
+
if (c.$error) {
|
|
4450
|
+
for (var type in c.$error) {
|
|
4451
|
+
if (c.$error.hasOwnProperty(type)) {
|
|
4452
|
+
switch (type) {
|
|
4453
|
+
case "required":
|
|
4454
|
+
$scope.whyDisabled += "Field missing required value. ";
|
|
4455
|
+
break;
|
|
4456
|
+
case "pattern":
|
|
4457
|
+
$scope.whyDisabled += "Field does not match required pattern. ";
|
|
4458
|
+
break;
|
|
4459
|
+
default:
|
|
4460
|
+
$scope.whyDisabled += type + ". ";
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
}
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
});
|
|
4468
|
+
}
|
|
4469
|
+
if ($scope[$scope.topLevelFormName]) {
|
|
4470
|
+
if ($scope[$scope.topLevelFormName].$invalid) {
|
|
4471
|
+
$scope.whyDisabled = 'The form data is invalid:';
|
|
4472
|
+
generateWhyDisabledMessage($scope[$scope.topLevelFormName]);
|
|
4473
|
+
}
|
|
4474
|
+
else if ($scope[$scope.topLevelFormName].$pristine) {
|
|
4475
|
+
// Don't have disabled message - should be obvious from Cancel being disabled,
|
|
4476
|
+
// and the message comes up when the Save button is clicked.
|
|
4477
|
+
pristine = true;
|
|
4478
|
+
}
|
|
3526
4479
|
}
|
|
3527
4480
|
else {
|
|
3528
|
-
|
|
4481
|
+
$scope.whyDisabled = "Top level form name invalid";
|
|
4482
|
+
}
|
|
4483
|
+
if (pristine || !!$scope.whyDisabled || $scope.phase !== "ready") {
|
|
4484
|
+
return true;
|
|
4485
|
+
}
|
|
4486
|
+
else if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isSaveDisabled) !== "function") {
|
|
4487
|
+
return false;
|
|
4488
|
+
}
|
|
4489
|
+
else {
|
|
4490
|
+
var retVal = $scope.disableFunctions.isSaveDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
4491
|
+
if (typeof retVal === "string") {
|
|
4492
|
+
$scope.whyDisabled = retVal;
|
|
4493
|
+
}
|
|
4494
|
+
else {
|
|
4495
|
+
$scope.whyDisabled = "An application level user-specified function is inhibiting saving the record";
|
|
4496
|
+
}
|
|
4497
|
+
return !!retVal;
|
|
3529
4498
|
}
|
|
3530
4499
|
};
|
|
3531
4500
|
$scope.isDeleteDisabled = function () {
|
|
3532
|
-
|
|
4501
|
+
var _a;
|
|
4502
|
+
if (!$scope.id || $scope.phase !== "ready") {
|
|
4503
|
+
return true;
|
|
4504
|
+
}
|
|
4505
|
+
else if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isDeleteDisabled) === "function") {
|
|
3533
4506
|
return $scope.disableFunctions.isDeleteDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
3534
4507
|
}
|
|
3535
4508
|
else {
|
|
3536
|
-
return
|
|
4509
|
+
return false;
|
|
3537
4510
|
}
|
|
3538
4511
|
};
|
|
3539
4512
|
$scope.isNewDisabled = function () {
|
|
3540
|
-
|
|
4513
|
+
var _a;
|
|
4514
|
+
if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isNewDisabled) === "function") {
|
|
3541
4515
|
return $scope.disableFunctions.isNewDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
3542
4516
|
}
|
|
3543
4517
|
else {
|
|
3544
4518
|
return false;
|
|
3545
4519
|
}
|
|
3546
4520
|
};
|
|
3547
|
-
$scope.
|
|
3548
|
-
|
|
3549
|
-
|
|
3550
|
-
|
|
3551
|
-
|
|
3552
|
-
|
|
4521
|
+
$scope.setDefaults = function (formSchema, base) {
|
|
4522
|
+
if (base === void 0) { base = ''; }
|
|
4523
|
+
var retVal = {};
|
|
4524
|
+
formSchema.forEach(function (s) {
|
|
4525
|
+
if (s.defaultValue !== undefined) {
|
|
4526
|
+
var nameParts = s.name.replace(base, '').split(".");
|
|
4527
|
+
var target = retVal;
|
|
4528
|
+
for (var i = 0; i < nameParts.length - 1; i++) {
|
|
4529
|
+
if (!target[nameParts[i]]) {
|
|
4530
|
+
target[nameParts[i]] = {};
|
|
4531
|
+
}
|
|
4532
|
+
target = target[nameParts[i]];
|
|
4533
|
+
}
|
|
4534
|
+
target[nameParts[nameParts.length - 1]] = s.defaultValue;
|
|
4535
|
+
}
|
|
4536
|
+
});
|
|
4537
|
+
return retVal;
|
|
3553
4538
|
};
|
|
3554
4539
|
$scope.getVal = function (expression, index) {
|
|
3555
|
-
if (expression.indexOf(
|
|
4540
|
+
if (expression.indexOf("$index") === -1 || typeof index !== "undefined") {
|
|
3556
4541
|
expression = expression.replace(/\$index/g, index);
|
|
3557
|
-
return $scope.$eval(
|
|
4542
|
+
return $scope.$eval("record." + expression);
|
|
3558
4543
|
}
|
|
3559
4544
|
//else {
|
|
3560
4545
|
// Used to show error here, but angular seems to call before record is populated sometimes
|
|
@@ -3562,11 +4547,42 @@ var fng;
|
|
|
3562
4547
|
//}
|
|
3563
4548
|
};
|
|
3564
4549
|
$scope.sortableOptions = {
|
|
3565
|
-
update: function () {
|
|
3566
|
-
if (
|
|
4550
|
+
update: function (e, ui) {
|
|
4551
|
+
if (e.target.hasAttribute("disabled")) {
|
|
4552
|
+
// where formsAngular.elemSecurityFuncBinding is set to "one-time" or "normal", the <ol> that the
|
|
4553
|
+
// ui-sortable directive has been used with will have an ng-disabled that may or may not have caused
|
|
4554
|
+
// a disabled attribute to be added to that element. in the case where this attribute has been
|
|
4555
|
+
// added, sorting should be prevented.
|
|
4556
|
+
// allowing the user to begin the drag, and then preventing it only once they release the mouse button,
|
|
4557
|
+
// doesn't seem like the best solution, but I've yet to find something that works better. the
|
|
4558
|
+
// cancel property (see commented-out code below) looks like it should work (and kind of does), but this
|
|
4559
|
+
// screws up mouse events on input fields hosted within the draggable <li> items, so you're
|
|
4560
|
+
// basically prevented from updating any form element in the nested schema
|
|
4561
|
+
ui.item.sortable.cancel();
|
|
4562
|
+
}
|
|
4563
|
+
else if ($scope.topLevelFormName) {
|
|
3567
4564
|
$scope[$scope.topLevelFormName].$setDirty();
|
|
3568
4565
|
}
|
|
4566
|
+
},
|
|
4567
|
+
// don't do this (see comment above)
|
|
4568
|
+
//cancel: "ol[disabled]>li"
|
|
4569
|
+
};
|
|
4570
|
+
$scope.setUpCustomLookupOptions = function (schemaElement, ids, options, baseScope) {
|
|
4571
|
+
for (var _i = 0, _a = [$scope, baseScope]; _i < _a.length; _i++) {
|
|
4572
|
+
var scope = _a[_i];
|
|
4573
|
+
if (scope) {
|
|
4574
|
+
// need to be accessible on our scope for generation of the select options, and - for nested schemas -
|
|
4575
|
+
// on baseScope for the conversion back to ids done by prepareForSave
|
|
4576
|
+
scope[schemaElement.ids] = ids;
|
|
4577
|
+
scope[schemaElement.options] = options;
|
|
4578
|
+
}
|
|
3569
4579
|
}
|
|
4580
|
+
var data = getData($scope.record, schemaElement.name);
|
|
4581
|
+
if (!data) {
|
|
4582
|
+
return;
|
|
4583
|
+
}
|
|
4584
|
+
data = convertForeignKeys(schemaElement, data, options, ids);
|
|
4585
|
+
setData($scope.record, schemaElement.name, undefined, data);
|
|
3570
4586
|
};
|
|
3571
4587
|
},
|
|
3572
4588
|
fillFormFromBackendCustomSchema: fillFormFromBackendCustomSchema,
|
|
@@ -3605,17 +4621,267 @@ var fng;
|
|
|
3605
4621
|
var services;
|
|
3606
4622
|
(function (services) {
|
|
3607
4623
|
/*@ngInject*/
|
|
3608
|
-
|
|
3609
|
-
function
|
|
4624
|
+
securityService.$inject = ["$rootScope"];
|
|
4625
|
+
function securityService($rootScope) {
|
|
4626
|
+
function canDoSecurity(type) {
|
|
4627
|
+
return (!!fng.formsAngular.elemSecurityFuncBinding &&
|
|
4628
|
+
((type === "hidden" && !!fng.formsAngular.hiddenSecurityFuncName) ||
|
|
4629
|
+
(type === "disabled" && !!fng.formsAngular.disabledSecurityFuncName)));
|
|
4630
|
+
}
|
|
4631
|
+
function canDoSecurityNow(scope, type) {
|
|
4632
|
+
return (canDoSecurity(type) && // we have security configured
|
|
4633
|
+
(
|
|
4634
|
+
// the host app has not (temporarily) disabled this security type (which it might do, as an optimisation, when there are
|
|
4635
|
+
// currently no security rules to apply); and
|
|
4636
|
+
// it has provided the callbacks that are specified in the security configuration; and
|
|
4637
|
+
// the provided scope (if any) has been decorated (by us). pages and popups which aren't form controllers will need to use
|
|
4638
|
+
// (either directly, or through formMarkupHelper), the decorateSecurableScope() function below
|
|
4639
|
+
(type === "hidden" &&
|
|
4640
|
+
$rootScope[fng.formsAngular.hiddenSecurityFuncName] &&
|
|
4641
|
+
(!scope || !!scope.isSecurelyHidden))
|
|
4642
|
+
||
|
|
4643
|
+
(type === "disabled" &&
|
|
4644
|
+
$rootScope[fng.formsAngular.disabledSecurityFuncName] &&
|
|
4645
|
+
(!scope || !!scope.isSecurelyDisabled))));
|
|
4646
|
+
}
|
|
4647
|
+
function isSecurelyHidden(elemId, pseudoUrl) {
|
|
4648
|
+
return $rootScope[fng.formsAngular.hiddenSecurityFuncName](elemId, pseudoUrl);
|
|
4649
|
+
}
|
|
4650
|
+
function getSecureDisabledState(elemId, pseudoUrl) {
|
|
4651
|
+
return $rootScope[fng.formsAngular.disabledSecurityFuncName](elemId, pseudoUrl);
|
|
4652
|
+
}
|
|
4653
|
+
function isSecurelyDisabled(elemId, pseudoUrl) {
|
|
4654
|
+
return !!getSecureDisabledState(elemId, pseudoUrl); // either true or "+"
|
|
4655
|
+
}
|
|
4656
|
+
function getBindingStr() {
|
|
4657
|
+
return fng.formsAngular.elemSecurityFuncBinding === "one-time" ? "::" : "";
|
|
4658
|
+
}
|
|
4659
|
+
function ignoreElemId(elemId) {
|
|
4660
|
+
var _a;
|
|
4661
|
+
return (_a = fng.formsAngular.ignoreIdsForHideableOrDisableableAttrs) === null || _a === void 0 ? void 0 : _a.some(function (id) { return elemId.includes(id); });
|
|
4662
|
+
}
|
|
4663
|
+
function getXableAttrs(elemId, attr) {
|
|
4664
|
+
if (elemId && attr && !ignoreElemId(elemId)) {
|
|
4665
|
+
return " ".concat(attr, " title=\"").concat(elemId, "\"");
|
|
4666
|
+
}
|
|
4667
|
+
else {
|
|
4668
|
+
return "";
|
|
4669
|
+
}
|
|
4670
|
+
}
|
|
4671
|
+
function getDisableableAttrs(elemId) {
|
|
4672
|
+
// even when an element should not actually be disabled, we should still mark what would otherwise have been a
|
|
4673
|
+
// potentially-disabled element with scope.disableableAttr - where this is set - and where it is set, also set its
|
|
4674
|
+
// title to be the same as its id so that users can learn of its id by hovering over it. this will
|
|
4675
|
+
// help anyone trying to figure out what is the right element id to use for a DOM security rule
|
|
4676
|
+
return getXableAttrs(elemId, fng.formsAngular.disableableAttr);
|
|
4677
|
+
}
|
|
4678
|
+
function getDisableableAncestorAttrs(elemId) {
|
|
4679
|
+
// even when an element should not actually be disabled, we should still mark what would otherwise have been a
|
|
4680
|
+
// potentially-disabled element with scope.disableableAttr - where this is set - and where it is set, also set its
|
|
4681
|
+
// title to be the same as its id so that users can learn of its id by hovering over it. this will
|
|
4682
|
+
// help anyone trying to figure out what is the right element id to use for a DOM security rule
|
|
4683
|
+
return getXableAttrs(elemId, fng.formsAngular.disableableAncestorAttr);
|
|
4684
|
+
}
|
|
4685
|
+
function getHideableAttrs(elemId) {
|
|
4686
|
+
// even when canDoSecurityNow() returns false, we should still mark what would otherwise have been a
|
|
4687
|
+
// potentially-hidden element with scope.hideableAttr, where this is set, and where it is set, also set its
|
|
4688
|
+
// title to be the same as its id so that users can learn of its id by hovering over it. this will
|
|
4689
|
+
// help anyone trying to figure out what is the right element id to use for a DOM security rule
|
|
4690
|
+
return getXableAttrs(elemId, fng.formsAngular.hideableAttr);
|
|
4691
|
+
}
|
|
4692
|
+
return {
|
|
4693
|
+
canDoSecurity: canDoSecurity,
|
|
4694
|
+
canDoSecurityNow: canDoSecurityNow,
|
|
4695
|
+
isSecurelyHidden: isSecurelyHidden,
|
|
4696
|
+
isSecurelyDisabled: isSecurelyDisabled,
|
|
4697
|
+
getHideableAttrs: getHideableAttrs,
|
|
4698
|
+
getDisableableAttrs: getDisableableAttrs,
|
|
4699
|
+
getDisableableAncestorAttrs: getDisableableAncestorAttrs,
|
|
4700
|
+
// whilst initialising new pages and popups, pass their scope here for decoration with functions that can be used to check
|
|
4701
|
+
// the disabled / hidden state of DOM elements on that page according to the prevailing security rules.
|
|
4702
|
+
// if the host app indicates that security checks should be skipped for this page, we will NOT assign the corresponding
|
|
4703
|
+
// functions. the presence of these functions will be checked later by canDoSecurityNow(), which will always be called
|
|
4704
|
+
// before any security logic is applied. this allows security to be bypassed entirely at the request of the host app,
|
|
4705
|
+
// providing an opportunity for optimisation.
|
|
4706
|
+
decorateSecurableScope: function (securableScope, params) {
|
|
4707
|
+
if (canDoSecurity("hidden") && (!fng.formsAngular.skipHiddenSecurityFuncName || (params === null || params === void 0 ? void 0 : params.overrideSkipping) || typeof $rootScope[fng.formsAngular.skipHiddenSecurityFuncName] !== "function" || !$rootScope[fng.formsAngular.skipHiddenSecurityFuncName](params === null || params === void 0 ? void 0 : params.pseudoUrl))) {
|
|
4708
|
+
securableScope.isSecurelyHidden = function (elemId) {
|
|
4709
|
+
return isSecurelyHidden(elemId, params === null || params === void 0 ? void 0 : params.pseudoUrl);
|
|
4710
|
+
};
|
|
4711
|
+
}
|
|
4712
|
+
if (canDoSecurity("disabled") && (!fng.formsAngular.skipDisabledSecurityFuncName || (params === null || params === void 0 ? void 0 : params.overrideSkipping) || typeof $rootScope[fng.formsAngular.skipDisabledSecurityFuncName] !== "function" || !$rootScope[fng.formsAngular.skipDisabledSecurityFuncName](params === null || params === void 0 ? void 0 : params.pseudoUrl))) {
|
|
4713
|
+
securableScope.isSecurelyDisabled = function (elemId) {
|
|
4714
|
+
return isSecurelyDisabled(elemId, params === null || params === void 0 ? void 0 : params.pseudoUrl);
|
|
4715
|
+
};
|
|
4716
|
+
if (!fng.formsAngular.skipDisabledAncestorSecurityFuncName || (params === null || params === void 0 ? void 0 : params.overrideSkipping) || typeof $rootScope[fng.formsAngular.skipDisabledAncestorSecurityFuncName] || !$rootScope[fng.formsAngular.skipDisabledAncestorSecurityFuncName](params === null || params === void 0 ? void 0 : params.pseudoUrl)) {
|
|
4717
|
+
securableScope.requiresDisabledChildren = function (elemId) {
|
|
4718
|
+
return getSecureDisabledState(elemId, params === null || params === void 0 ? void 0 : params.pseudoUrl) === "+";
|
|
4719
|
+
};
|
|
4720
|
+
}
|
|
4721
|
+
}
|
|
4722
|
+
},
|
|
4723
|
+
doSecurityWhenReady: function (cb) {
|
|
4724
|
+
if (canDoSecurityNow(undefined, "hidden")) {
|
|
4725
|
+
cb();
|
|
4726
|
+
}
|
|
4727
|
+
else if (canDoSecurity("hidden")) {
|
|
4728
|
+
// wait until the hidden security function has been provided (externally) before proceeding with the callback...
|
|
4729
|
+
// we assume here that the hidden security and disabled security functions are both going to be provided at the
|
|
4730
|
+
// same time (and could therefore watch for either of these things)
|
|
4731
|
+
var unwatch_2 = $rootScope.$watch(fng.formsAngular.hiddenSecurityFuncName, function (newValue) {
|
|
4732
|
+
if (newValue) {
|
|
4733
|
+
unwatch_2();
|
|
4734
|
+
cb();
|
|
4735
|
+
}
|
|
4736
|
+
});
|
|
4737
|
+
}
|
|
4738
|
+
},
|
|
4739
|
+
considerVisibility: function (id, scope) {
|
|
4740
|
+
var hideableAttrs = getHideableAttrs(id);
|
|
4741
|
+
if (canDoSecurityNow(scope, "hidden")) {
|
|
4742
|
+
if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
|
|
4743
|
+
if (scope.isSecurelyHidden(id)) {
|
|
4744
|
+
// if our securityFunc supports instant binding and evaluates to true, then nothing needs to be
|
|
4745
|
+
// added to the dom for this field, which we indicate to our caller as follows...
|
|
4746
|
+
return { omit: true };
|
|
4747
|
+
}
|
|
4748
|
+
}
|
|
4749
|
+
else {
|
|
4750
|
+
return { visibilityAttr: "data-ng-if=\"".concat(getBindingStr(), "!isSecurelyHidden('").concat(id, "')\"").concat(hideableAttrs) };
|
|
4751
|
+
}
|
|
4752
|
+
}
|
|
4753
|
+
return { visibilityAttr: hideableAttrs };
|
|
4754
|
+
},
|
|
4755
|
+
// consider the visibility of a container whose visibility depends upon at least some of its content being visible.
|
|
4756
|
+
// the container is assumed not to itself be securable (hence it doesn't have an id - or at least, not one we're
|
|
4757
|
+
// concerned about - and it doesn't itself need a "hideable" attribute)
|
|
4758
|
+
considerContainerVisibility: function (contentIds, scope) {
|
|
4759
|
+
if (canDoSecurityNow(scope, "hidden")) {
|
|
4760
|
+
if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
|
|
4761
|
+
if (contentIds.some(function (id) { return !scope.isSecurelyHidden(id); })) {
|
|
4762
|
+
return {};
|
|
4763
|
+
}
|
|
4764
|
+
else {
|
|
4765
|
+
return { omit: true };
|
|
4766
|
+
}
|
|
4767
|
+
}
|
|
4768
|
+
else {
|
|
4769
|
+
var attrs = contentIds.map(function (id) { return "!isSecurelyHidden('".concat(id, "')"); });
|
|
4770
|
+
return { visibilityAttr: "data-ng-if=\"".concat(getBindingStr(), "(").concat(attrs.join(" || "), ")\"") };
|
|
4771
|
+
}
|
|
4772
|
+
}
|
|
4773
|
+
return {};
|
|
4774
|
+
},
|
|
4775
|
+
// Generate an attribute that could be added to the element with the given id to enable or disable it
|
|
4776
|
+
// according to the prevailing security rules. In most cases, the attribute will either be "disabled"
|
|
4777
|
+
// (in the case of 'instant' binding) or data-ng-disabled="xxxx" (in the case of one-time or normal
|
|
4778
|
+
// binding). For directives that require something different, use params:
|
|
4779
|
+
// - forceNg will wrap a positive (disabled) result in an angular directive (e.g., data-ng-disabled="true")
|
|
4780
|
+
// rather than returning simply "disabled"
|
|
4781
|
+
// - attrRequiresValue will translate a positive (disabled) result into an attribute with a truthy value
|
|
4782
|
+
// (e.g., disabled="true") rather than returning simply "disabled"
|
|
4783
|
+
// - attr can be used in the case where a directive expects an attribute other than "disabled".
|
|
4784
|
+
// (for example, uib-tab expects "disable").
|
|
4785
|
+
// Even if the element is not be disabled on this occasion, we will always return the value of
|
|
4786
|
+
// fng.formsAngular.disableableAttr - where set - as a way of marking it as potentially disableable.
|
|
4787
|
+
// Because they can also have a readonly attribute which needs to be taken into consideration, this
|
|
4788
|
+
// function is NOT suitable for fields, which are instead handled by fieldformMarkupHelper.handleReadOnlyDisabled().
|
|
4789
|
+
generateDisabledAttr: function (id, scope, params) {
|
|
4790
|
+
function getActuallyDisabledAttrs() {
|
|
4791
|
+
var result = "";
|
|
4792
|
+
if (canDoSecurityNow(scope, "disabled")) {
|
|
4793
|
+
if (!params) {
|
|
4794
|
+
params = {};
|
|
4795
|
+
}
|
|
4796
|
+
if (params.attrRequiresValue && params.forceNg) {
|
|
4797
|
+
throw new Error("Invalid combination of parameters provided to generateDisabledAttr() [attrRequiresValue and forceNg]");
|
|
4798
|
+
}
|
|
4799
|
+
var attr = params.attr || "disabled";
|
|
4800
|
+
if (fng.formsAngular.elemSecurityFuncBinding === "instant") {
|
|
4801
|
+
if (scope.isSecurelyDisabled(id)) {
|
|
4802
|
+
if (params.attrRequiresValue) {
|
|
4803
|
+
return " ".concat(attr, "=\"true\"");
|
|
4804
|
+
}
|
|
4805
|
+
else if (params.forceNg) {
|
|
4806
|
+
result = "true";
|
|
4807
|
+
}
|
|
4808
|
+
else {
|
|
4809
|
+
return " ".concat(attr);
|
|
4810
|
+
}
|
|
4811
|
+
}
|
|
4812
|
+
}
|
|
4813
|
+
else {
|
|
4814
|
+
result = "".concat(getBindingStr(), "isSecurelyDisabled('").concat(id, "')");
|
|
4815
|
+
}
|
|
4816
|
+
if (result) {
|
|
4817
|
+
if (attr === "disabled") {
|
|
4818
|
+
return " data-ng-disabled=\"".concat(result, "\"");
|
|
4819
|
+
}
|
|
4820
|
+
else {
|
|
4821
|
+
return " data-ng-attr-".concat(attr, "=\"").concat(result, "\"");
|
|
4822
|
+
}
|
|
4823
|
+
}
|
|
4824
|
+
}
|
|
4825
|
+
return result;
|
|
4826
|
+
}
|
|
4827
|
+
return getActuallyDisabledAttrs() + getDisableableAttrs(id);
|
|
4828
|
+
},
|
|
4829
|
+
};
|
|
4830
|
+
}
|
|
4831
|
+
services.securityService = securityService;
|
|
4832
|
+
})(services = fng.services || (fng.services = {}));
|
|
4833
|
+
})(fng || (fng = {}));
|
|
4834
|
+
/// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
|
|
4835
|
+
var ExpirationCache = /** @class */ (function () {
|
|
4836
|
+
function ExpirationCache(timeout) {
|
|
4837
|
+
if (timeout === void 0) { timeout = 60 * 1000; }
|
|
4838
|
+
this.store = new Map();
|
|
4839
|
+
this.timeout = timeout;
|
|
4840
|
+
}
|
|
4841
|
+
ExpirationCache.prototype.get = function (key) {
|
|
4842
|
+
// this.store.has(key) ? console.log(`cache hit`) : console.log(`cache miss`);
|
|
4843
|
+
return this.store.get(key);
|
|
4844
|
+
};
|
|
4845
|
+
ExpirationCache.prototype.put = function (key, val) {
|
|
4846
|
+
var _this = this;
|
|
4847
|
+
this.store.set(key, val);
|
|
4848
|
+
// remove it once it's expired
|
|
4849
|
+
setTimeout(function () {
|
|
4850
|
+
// console.log(`removing expired key ${key}`);
|
|
4851
|
+
_this.remove(key);
|
|
4852
|
+
}, this.timeout);
|
|
4853
|
+
};
|
|
4854
|
+
ExpirationCache.prototype.remove = function (key) {
|
|
4855
|
+
this.store.delete(key);
|
|
4856
|
+
};
|
|
4857
|
+
ExpirationCache.prototype.removeAll = function () {
|
|
4858
|
+
this.store = new Map();
|
|
4859
|
+
};
|
|
4860
|
+
ExpirationCache.prototype.delete = function () {
|
|
4861
|
+
//no op here because this is standalone, not a part of $cacheFactory
|
|
4862
|
+
};
|
|
4863
|
+
return ExpirationCache;
|
|
4864
|
+
}());
|
|
4865
|
+
var fng;
|
|
4866
|
+
(function (fng) {
|
|
4867
|
+
var services;
|
|
4868
|
+
(function (services) {
|
|
4869
|
+
/*@ngInject*/
|
|
4870
|
+
SubmissionsService.$inject = ["$http"];
|
|
4871
|
+
function SubmissionsService($http) {
|
|
4872
|
+
var useCacheForGetAll = true;
|
|
4873
|
+
var expCache = new ExpirationCache();
|
|
3610
4874
|
/*
|
|
3611
4875
|
generate a query string for a filtered and paginated query for submissions.
|
|
3612
4876
|
options consists of the following:
|
|
3613
4877
|
{
|
|
3614
4878
|
aggregate - whether or not to aggregate results (http://docs.mongodb.org/manual/aggregation/)
|
|
3615
4879
|
find - find parameter
|
|
4880
|
+
projection - the fields to return
|
|
3616
4881
|
limit - limit results to this number of records
|
|
3617
4882
|
skip - skip this number of records before returning results
|
|
3618
4883
|
order - sort order
|
|
4884
|
+
concatenate - whether to concatenate all of the list fields into a single text field (and return { id, text }[]), or not (in which case the documents - albeit only list fields and _id - are returned without transformation)
|
|
3619
4885
|
}
|
|
3620
4886
|
*/
|
|
3621
4887
|
var generateListQuery = function (options) {
|
|
@@ -3636,11 +4902,23 @@ var fng;
|
|
|
3636
4902
|
};
|
|
3637
4903
|
addParameter('l', options.limit);
|
|
3638
4904
|
addParameter('f', options.find);
|
|
4905
|
+
addParameter('p', options.projection);
|
|
3639
4906
|
addParameter('a', options.aggregate);
|
|
3640
4907
|
addParameter('o', options.order);
|
|
3641
4908
|
addParameter('s', options.skip);
|
|
4909
|
+
addParameter('c', options.concatenate);
|
|
3642
4910
|
return queryString;
|
|
3643
4911
|
};
|
|
4912
|
+
function generateUrl(modelName, formName, id) {
|
|
4913
|
+
var url = "/api/".concat(modelName);
|
|
4914
|
+
if (formName) {
|
|
4915
|
+
url += "/".concat(formName);
|
|
4916
|
+
}
|
|
4917
|
+
if (id) {
|
|
4918
|
+
url += "/".concat(id);
|
|
4919
|
+
}
|
|
4920
|
+
return url;
|
|
4921
|
+
}
|
|
3644
4922
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
3645
4923
|
//
|
|
3646
4924
|
// interface ITabChange {
|
|
@@ -3663,20 +4941,48 @@ var fng;
|
|
|
3663
4941
|
// changed: changed
|
|
3664
4942
|
// };
|
|
3665
4943
|
// },
|
|
3666
|
-
|
|
3667
|
-
|
|
3668
|
-
|
|
3669
|
-
|
|
4944
|
+
// return only the list attributes for the given record. where returnRaw is true, the record's
|
|
4945
|
+
// list attributes will be returned without transformation. otherwise, the list attributes will be concatenated
|
|
4946
|
+
// (with spaces) and returned in the form { list: string }
|
|
4947
|
+
getListAttributes: function (ref, id, returnRaw) {
|
|
4948
|
+
var actualId = typeof id === "string" ? id : id.id || id._id || id.x || id;
|
|
4949
|
+
if (typeof actualId === "object") {
|
|
4950
|
+
throw new Error("getListAttributes doesn't expect an object but was provided with ".concat(JSON.stringify(id)));
|
|
3670
4951
|
}
|
|
3671
|
-
|
|
4952
|
+
var queryString = returnRaw ? "?returnRaw=1" : "";
|
|
4953
|
+
return $http.get("/api/".concat(ref, "/").concat(actualId, "/list").concat(queryString), { cache: expCache });
|
|
4954
|
+
},
|
|
4955
|
+
// return only the list attributes for ALL records in the given collection, returning ILookupItem[]
|
|
4956
|
+
getAllListAttributes: function (ref) {
|
|
4957
|
+
return $http.get("/api/".concat(ref, "/listAll"), { cache: expCache });
|
|
3672
4958
|
},
|
|
3673
|
-
|
|
4959
|
+
// return only the list attributes for records in the given collection that satisfy the given query conditions (filter, limit etc.)
|
|
4960
|
+
// return ILookupItem[] if options.concatenate is true, else the raw documents
|
|
4961
|
+
getPagedAndFilteredList: function (ref, options) {
|
|
4962
|
+
if (options.projection) {
|
|
4963
|
+
throw new Error("Cannot use projection option for getPagedAndFilteredList, because it only returns list fields");
|
|
4964
|
+
}
|
|
4965
|
+
if (options.concatenate === undefined) {
|
|
4966
|
+
options.concatenate = false;
|
|
4967
|
+
}
|
|
4968
|
+
return $http.get("/api/".concat(ref, "/listAll").concat(generateListQuery(options)));
|
|
4969
|
+
},
|
|
4970
|
+
// return ALL attributes for records in the given collection that satisfy the given query conditions (filter, limit etc.)
|
|
4971
|
+
getPagedAndFilteredListFull: function (ref, options) {
|
|
4972
|
+
return $http.get("/api/".concat(ref).concat(generateListQuery(options)));
|
|
4973
|
+
},
|
|
4974
|
+
readRecord: function (modelName, id, formName) {
|
|
3674
4975
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
3675
4976
|
// let retVal;
|
|
3676
4977
|
// if (tabChangeData && tabChangeData.model === modelName && tabChangeData.id === id) {
|
|
3677
4978
|
// retVal = Promise.resolve({data:tabChangeData.record, changed: tabChangeData.changed, master: tabChangeData.master});
|
|
3678
4979
|
// } else {
|
|
3679
|
-
|
|
4980
|
+
var actualId = typeof id === "string" ? id : id.id || id._id || id.x || id;
|
|
4981
|
+
if (typeof actualId === "object") {
|
|
4982
|
+
throw new Error("readRecord doesn't expect an object but was provided with ".concat(JSON.stringify(id)));
|
|
4983
|
+
}
|
|
4984
|
+
var url = generateUrl(modelName, formName, actualId);
|
|
4985
|
+
return $http.get(url);
|
|
3680
4986
|
// retVal = $http.get('/api/' + modelName + '/' + id);
|
|
3681
4987
|
// }
|
|
3682
4988
|
// tabChangeData = null;
|
|
@@ -3684,23 +4990,32 @@ var fng;
|
|
|
3684
4990
|
},
|
|
3685
4991
|
getAll: function (modelName, _options) {
|
|
3686
4992
|
var options = angular.extend({
|
|
3687
|
-
cache:
|
|
4993
|
+
cache: useCacheForGetAll ? expCache : false
|
|
3688
4994
|
}, _options);
|
|
3689
|
-
return $http.get(
|
|
4995
|
+
return $http.get("/api/".concat(modelName), options);
|
|
3690
4996
|
},
|
|
3691
|
-
|
|
3692
|
-
|
|
4997
|
+
deleteRecord: function (modelName, id, formName) {
|
|
4998
|
+
var url = generateUrl(modelName, formName, id);
|
|
4999
|
+
return $http.delete(url);
|
|
3693
5000
|
},
|
|
3694
|
-
|
|
3695
|
-
|
|
5001
|
+
updateRecord: function (modelName, id, dataToSave, formName) {
|
|
5002
|
+
expCache.remove("/api/".concat(modelName));
|
|
5003
|
+
var url = generateUrl(modelName, formName, id);
|
|
5004
|
+
return $http.post(url, dataToSave);
|
|
3696
5005
|
},
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
5006
|
+
createRecord: function (modelName, dataToSave, formName) {
|
|
5007
|
+
expCache.remove("/api/".concat(modelName));
|
|
5008
|
+
var url = generateUrl(modelName, formName) + "/new";
|
|
5009
|
+
return $http.post(url, dataToSave);
|
|
3700
5010
|
},
|
|
3701
|
-
|
|
3702
|
-
|
|
3703
|
-
|
|
5011
|
+
useCache: function (val) {
|
|
5012
|
+
useCacheForGetAll = val;
|
|
5013
|
+
},
|
|
5014
|
+
getCache: function () {
|
|
5015
|
+
return !!expCache;
|
|
5016
|
+
},
|
|
5017
|
+
clearCache: function () {
|
|
5018
|
+
expCache.removeAll();
|
|
3704
5019
|
}
|
|
3705
5020
|
};
|
|
3706
5021
|
}
|
|
@@ -3726,6 +5041,7 @@ var fng;
|
|
|
3726
5041
|
fngInvalidRequired: 'fng-invalid-required',
|
|
3727
5042
|
allowLocationChange: true // Set when the data arrives..
|
|
3728
5043
|
};
|
|
5044
|
+
$scope.errorVisible = false;
|
|
3729
5045
|
angular.extend($scope, routingService.parsePathFunc()($location.$$path));
|
|
3730
5046
|
// Load context menu. For /person/client/:id/edit we need
|
|
3731
5047
|
// to load PersonCtrl and PersonClientCtrl
|
|
@@ -3740,17 +5056,33 @@ var fng;
|
|
|
3740
5056
|
$rootScope.$broadcast('fngFormLoadStart', $scope);
|
|
3741
5057
|
formGenerator.decorateScope($scope, formGenerator, recordHandler, $scope.sharedData);
|
|
3742
5058
|
recordHandler.decorateScope($scope, $uibModal, recordHandler, ctrlState);
|
|
3743
|
-
|
|
3744
|
-
|
|
3745
|
-
|
|
3746
|
-
|
|
3747
|
-
$scope.sharedData.modelControllers[i].onBaseCtrlReady
|
|
5059
|
+
function processTheForm() {
|
|
5060
|
+
recordHandler.fillFormWithBackendSchema($scope, formGenerator, recordHandler, ctrlState);
|
|
5061
|
+
// Tell the 'model controllers' that they can start fiddling with baseScope
|
|
5062
|
+
for (var i = 0; i < $scope.sharedData.modelControllers.length; i++) {
|
|
5063
|
+
if ($scope.sharedData.modelControllers[i].onBaseCtrlReady) {
|
|
5064
|
+
$scope.sharedData.modelControllers[i].onBaseCtrlReady($scope);
|
|
5065
|
+
}
|
|
3748
5066
|
}
|
|
5067
|
+
$scope.$on('$destroy', function () {
|
|
5068
|
+
$scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
|
|
5069
|
+
$rootScope.$broadcast('fngControllersUnloaded');
|
|
5070
|
+
});
|
|
5071
|
+
}
|
|
5072
|
+
//Check that we are ready
|
|
5073
|
+
if (typeof fng.formsAngular.beforeProcess === "function") {
|
|
5074
|
+
fng.formsAngular.beforeProcess($scope, function (err) {
|
|
5075
|
+
if (err) {
|
|
5076
|
+
$scope.showError(err.message, 'Error preparing to process form');
|
|
5077
|
+
}
|
|
5078
|
+
else {
|
|
5079
|
+
processTheForm();
|
|
5080
|
+
}
|
|
5081
|
+
});
|
|
5082
|
+
}
|
|
5083
|
+
else {
|
|
5084
|
+
processTheForm();
|
|
3749
5085
|
}
|
|
3750
|
-
$scope.$on('$destroy', function () {
|
|
3751
|
-
$scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
|
|
3752
|
-
$rootScope.$broadcast('fngControllersUnloaded');
|
|
3753
|
-
});
|
|
3754
5086
|
}
|
|
3755
5087
|
controllers.BaseCtrl = BaseCtrl;
|
|
3756
5088
|
})(controllers = fng.controllers || (fng.controllers = {}));
|
|
@@ -3806,13 +5138,20 @@ var fng;
|
|
|
3806
5138
|
var controllers;
|
|
3807
5139
|
(function (controllers) {
|
|
3808
5140
|
/*@ngInject*/
|
|
3809
|
-
NavCtrl.$inject = ["$
|
|
3810
|
-
function NavCtrl($
|
|
5141
|
+
NavCtrl.$inject = ["$rootScope", "$window", "$scope", "$filter", "routingService", "cssFrameworkService", "securityService"];
|
|
5142
|
+
function NavCtrl($rootScope, $window, $scope, $filter, routingService, cssFrameworkService, securityService) {
|
|
3811
5143
|
function clearContextMenu() {
|
|
3812
5144
|
$scope.items = [];
|
|
3813
5145
|
$scope.contextMenu = undefined;
|
|
5146
|
+
$scope.contextMenuId = undefined;
|
|
5147
|
+
$scope.contextMenuHidden = undefined;
|
|
5148
|
+
$scope.contextMenuDisabled = undefined;
|
|
3814
5149
|
}
|
|
5150
|
+
$rootScope.navScope = $scope; // Lets plugins access menus
|
|
3815
5151
|
clearContextMenu();
|
|
5152
|
+
$scope.toggleCollapsed = function () {
|
|
5153
|
+
$scope.collapsed = !$scope.collapsed;
|
|
5154
|
+
};
|
|
3816
5155
|
/* isCollapsed and showShortcuts are used to control how the menu is displayed in a responsive environment and whether the shortcut keystrokes help should be displayed */
|
|
3817
5156
|
$scope.isCollapsed = true;
|
|
3818
5157
|
$scope.showShortcuts = false;
|
|
@@ -3840,8 +5179,8 @@ var fng;
|
|
|
3840
5179
|
}
|
|
3841
5180
|
}
|
|
3842
5181
|
function filter(event) {
|
|
3843
|
-
var tagName = (event.target
|
|
3844
|
-
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
|
|
5182
|
+
var tagName = (event.target).tagName;
|
|
5183
|
+
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA' || tagName == "DIV" && event.target.classList.contains('ck-editor__editable'));
|
|
3845
5184
|
}
|
|
3846
5185
|
//console.log(event.keyCode, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey);
|
|
3847
5186
|
if (event.keyCode === 191 && (filter(event) || (event.ctrlKey && !event.altKey && !event.metaKey))) {
|
|
@@ -3870,7 +5209,7 @@ var fng;
|
|
|
3870
5209
|
else if (event.keyCode === 45 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
|
|
3871
5210
|
deferredBtnClick('newButton'); // Ctrl+Shift+Ins creates New record
|
|
3872
5211
|
}
|
|
3873
|
-
else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && event.altKey && !event.metaKey) {
|
|
5212
|
+
else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
|
|
3874
5213
|
deferredBtnClick('deleteButton'); // Ctrl+Shift+X deletes record
|
|
3875
5214
|
}
|
|
3876
5215
|
};
|
|
@@ -3884,8 +5223,27 @@ var fng;
|
|
|
3884
5223
|
}
|
|
3885
5224
|
return result;
|
|
3886
5225
|
};
|
|
5226
|
+
function initialiseContextMenu(menuCaption) {
|
|
5227
|
+
$scope.contextMenu = menuCaption;
|
|
5228
|
+
var menuId = "".concat(_.camelCase(menuCaption), "ContextMenu");
|
|
5229
|
+
// the context menu itself (see dropdown.ts) has an ng-if that checks for a value of
|
|
5230
|
+
// contextMenuId. let's delete this until we know we're ready to evaluate the security
|
|
5231
|
+
// of the menu items...
|
|
5232
|
+
$scope.contextMenuId = undefined;
|
|
5233
|
+
securityService.doSecurityWhenReady(function () {
|
|
5234
|
+
//... which we now are
|
|
5235
|
+
$scope.contextMenuId = menuId;
|
|
5236
|
+
$scope.contextMenuHidden = securityService.isSecurelyHidden($scope.contextMenuId);
|
|
5237
|
+
$scope.contextMenuDisabled = securityService.isSecurelyDisabled($scope.contextMenuId);
|
|
5238
|
+
});
|
|
5239
|
+
}
|
|
3887
5240
|
$scope.$on('fngControllersLoaded', function (evt, sharedData, modelName) {
|
|
3888
|
-
|
|
5241
|
+
initialiseContextMenu(sharedData.dropDownDisplay || sharedData.modelNameDisplay || $filter('titleCase')(modelName, false));
|
|
5242
|
+
if (sharedData.dropDownDisplayPromise) {
|
|
5243
|
+
sharedData.dropDownDisplayPromise.then(function (value) {
|
|
5244
|
+
initialiseContextMenu(value);
|
|
5245
|
+
});
|
|
5246
|
+
}
|
|
3889
5247
|
});
|
|
3890
5248
|
$scope.$on('fngControllersUnloaded', function (evt) {
|
|
3891
5249
|
clearContextMenu();
|
|
@@ -3901,28 +5259,60 @@ var fng;
|
|
|
3901
5259
|
}
|
|
3902
5260
|
else {
|
|
3903
5261
|
// Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
|
|
3904
|
-
var args = item.args || []
|
|
3905
|
-
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
|
|
3911
|
-
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
|
|
3917
|
-
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
|
|
5262
|
+
var args = item.args || [];
|
|
5263
|
+
var fn = item.fn;
|
|
5264
|
+
if (typeof fn === "function") {
|
|
5265
|
+
switch (args.length) {
|
|
5266
|
+
case 0:
|
|
5267
|
+
fn();
|
|
5268
|
+
break;
|
|
5269
|
+
case 1:
|
|
5270
|
+
fn(args[0]);
|
|
5271
|
+
break;
|
|
5272
|
+
case 2:
|
|
5273
|
+
fn(args[0], args[1]);
|
|
5274
|
+
break;
|
|
5275
|
+
case 3:
|
|
5276
|
+
fn(args[0], args[1], args[2]);
|
|
5277
|
+
break;
|
|
5278
|
+
case 4:
|
|
5279
|
+
fn(args[0], args[1], args[2], args[3]);
|
|
5280
|
+
break;
|
|
5281
|
+
}
|
|
5282
|
+
}
|
|
5283
|
+
else if (fn) {
|
|
5284
|
+
throw new Error("Incorrect menu setup");
|
|
3921
5285
|
}
|
|
3922
5286
|
}
|
|
3923
5287
|
};
|
|
3924
5288
|
$scope.isHidden = function (index) {
|
|
3925
|
-
|
|
5289
|
+
function explicitlyHidden(item) {
|
|
5290
|
+
return item.isHidden ? item.isHidden() : false;
|
|
5291
|
+
}
|
|
5292
|
+
var dividerHide = false;
|
|
5293
|
+
var item = $scope.items[index];
|
|
5294
|
+
// Hide a divider if it appears under another
|
|
5295
|
+
if (item.divider) {
|
|
5296
|
+
if (index === 0) {
|
|
5297
|
+
dividerHide = true;
|
|
5298
|
+
}
|
|
5299
|
+
else {
|
|
5300
|
+
var foundVisible = false;
|
|
5301
|
+
var check = index - 1;
|
|
5302
|
+
while (check >= 0 && !dividerHide && !foundVisible) {
|
|
5303
|
+
if ($scope.items[check].divider) {
|
|
5304
|
+
dividerHide = true;
|
|
5305
|
+
}
|
|
5306
|
+
else if (!explicitlyHidden($scope.items[check])) {
|
|
5307
|
+
foundVisible = true;
|
|
5308
|
+
}
|
|
5309
|
+
else {
|
|
5310
|
+
--check;
|
|
5311
|
+
}
|
|
5312
|
+
}
|
|
5313
|
+
}
|
|
5314
|
+
}
|
|
5315
|
+
return dividerHide || explicitlyHidden(item);
|
|
3926
5316
|
};
|
|
3927
5317
|
$scope.isDisabled = function (index) {
|
|
3928
5318
|
return $scope.items[index].isDisabled ? $scope.items[index].isDisabled() : false;
|
|
@@ -3941,6 +5331,10 @@ var fng;
|
|
|
3941
5331
|
}
|
|
3942
5332
|
return thisClass;
|
|
3943
5333
|
};
|
|
5334
|
+
var originalTitle = $window.document.title;
|
|
5335
|
+
$scope.$on('$routeChangeSuccess', function () {
|
|
5336
|
+
$window.document.title = originalTitle;
|
|
5337
|
+
});
|
|
3944
5338
|
}
|
|
3945
5339
|
controllers.NavCtrl = NavCtrl;
|
|
3946
5340
|
})(controllers = fng.controllers || (fng.controllers = {}));
|
|
@@ -3981,6 +5375,7 @@ var fng;
|
|
|
3981
5375
|
])
|
|
3982
5376
|
.controller('BaseCtrl', fng.controllers.BaseCtrl)
|
|
3983
5377
|
.controller('SaveChangesModalCtrl', fng.controllers.SaveChangesModalCtrl)
|
|
5378
|
+
.controller('LinkCtrl', fng.controllers.LinkCtrl)
|
|
3984
5379
|
.controller('ModelCtrl', fng.controllers.ModelCtrl)
|
|
3985
5380
|
.controller('NavCtrl', fng.controllers.NavCtrl)
|
|
3986
5381
|
.directive('modelControllerDropdown', fng.directives.modelControllerDropdown)
|
|
@@ -3992,6 +5387,7 @@ var fng;
|
|
|
3992
5387
|
.directive('fngNakedDate', fng.directives.fngNakedDate)
|
|
3993
5388
|
.filter('camelCase', fng.filters.camelCase)
|
|
3994
5389
|
.filter('titleCase', fng.filters.titleCase)
|
|
5390
|
+
.filter('extractTimestampFromMongoID', fng.filters.extractTimestampFromMongoID)
|
|
3995
5391
|
.service('addAllService', fng.services.addAllService)
|
|
3996
5392
|
.provider('cssFrameworkService', fng.services.cssFrameworkService)
|
|
3997
5393
|
.provider('routingService', fng.services.routingService)
|
|
@@ -4002,16 +5398,21 @@ var fng;
|
|
|
4002
5398
|
.factory('pluginHelper', fng.services.pluginHelper)
|
|
4003
5399
|
.factory('recordHandler', fng.services.recordHandler)
|
|
4004
5400
|
.factory('SchemasService', fng.services.SchemasService)
|
|
4005
|
-
.factory('SubmissionsService', fng.services.SubmissionsService)
|
|
5401
|
+
.factory('SubmissionsService', fng.services.SubmissionsService)
|
|
5402
|
+
.factory('securityService', fng.services.securityService);
|
|
4006
5403
|
})(fng || (fng = {}));
|
|
4007
5404
|
// expose the library
|
|
4008
5405
|
var formsAngular = fng.formsAngular;
|
|
4009
5406
|
|
|
4010
|
-
angular.module('formsAngular').run(['$templateCache', function($templateCache) {$templateCache.put('base-analysis.html','<div ng-controller="AnalysisCtrl">\n <div class="container-fluid page-header report-header">\n <div ng-class="css(\'rowFluid\')">\n <div class="header-lhs col-xs-7 span7">\n <h1>{{
|
|
4011
|
-
$templateCache.put('base-edit.html','<div ng-controller="BaseCtrl">\n <div ng-class="css(\'rowFluid\')" class="page-header edit-header">\n <div class="header-lhs col-sm-8 span8">\n <
|
|
4012
|
-
$templateCache.put('base-list.html','<div ng-controller="BaseCtrl">\n <div ng-class="css(\'rowFluid\')" class="page-header list-header">\n <div class="header-lhs col-sm-8 span8">\n <h1>{{modelNameDisplay}}</h1>\n </div>\n
|
|
5407
|
+
angular.module('formsAngular').run(['$templateCache', function($templateCache) {$templateCache.put('base-analysis.html','<div ng-controller="AnalysisCtrl">\n <error-display></error-display>\n <div ng-hide="!showLoading" class="loading">Loading…</div>\n <div class="container-fluid page-header report-header">\n <div ng-class="css(\'rowFluid\')">\n <div class="header-lhs col-xs-7 span7">\n <h1>{{ titleWithSubstitutions }}</h1>\n </div>\n <div class="header-rhs col-xs-5 span5">\n <form-input schema="paramSchema" name="paramForm" ng-show="paramSchema" formstyle="horizontalCompact"></form-input>\n </div>\n </div>\n </div>\n <div class="container-fluid page-body report-body">\n <div class="row-fluid report-grow">\n <div class="gridStyle" style="height:100%;" ui-grid="gridOptions" ui-grid-selection ui-grid-resize-columns></div>\n </div>\n </div>\n</div>\n');
|
|
5408
|
+
$templateCache.put('base-edit.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-hide="phase == \'ready\' && !showLoading" class="loading">Loading…</div>\n <div ng-class="css(\'rowFluid\')" class="page-header edit-header">\n <div class="header-lhs col-sm-8 span8">\n <h1 id="header-text">{{modelNameDisplay}} :\n <span id="header-data-desc">\n <span ng-show="!!editFormHeader" >{{ editFormHeader() }}</span>\n <span ng-hide="!!editFormHeader" ng-repeat="field in listSchema" ng-bind-html="getListData(record, field.name) + \' \'"></span>\n </span>\n </h1>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <div form-buttons></div>\n </div>\n </div>\n <div class="container-fluid page-body edit-body">\n <form-input name="baseForm" schema="baseSchema()" formstyle="compact"></form-input>\n </div>\n<!-- <pre>-->\n <!--Record-->\n <!--{{ record | json }}-->\n <!--formSchema-->\n <!--{{ formSchema | json }}-->\n<!-- </pre>-->\n</div>\n');
|
|
5409
|
+
$templateCache.put('base-list-view.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-hide="!showLoading" class="loading">Loading…</div>\n <div ng-class="css(\'rowFluid\')" class="page-header list-header">\n <div class="header-lhs col-sm-8 span8">\n <h1>{{modelNameDisplay}}</h1>\n </div>\n </div>\n <div class="page-body list-body">\n <div ng-class="css(\'rowFluid\')" infinite-scroll="scrollTheList()">\n <a ng-repeat="record in recordList" ng-href="{{generateViewUrl(record)}}">\n <div class="list-item">\n <div ng-class="css(\'span\',12/listSchema.length)" ng-repeat="field in listSchema">{{getListData(record, field.name)}} </div>\n </div>\n </a>\n </div>\n </div>\n</div>\n');
|
|
5410
|
+
$templateCache.put('base-list.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-hide="!showLoading" class="loading">Loading…</div>\n <div ng-class="css(\'rowFluid\')" class="page-header list-header">\n <div class="header-lhs col-sm-8 span8">\n <h1>{{modelNameDisplay}}</h1>\n </div>\n <div class="header-rhs col-sm-2 span2">\n <a ng-href="{{generateNewUrl()}}"><button id="newBtn" class="btn btn-default"><i class="icon-plus"></i> New</button></a>\n </div>\n </div>\n <div class="page-body list-body">\n <div ng-class="css(\'rowFluid\')" infinite-scroll="scrollTheList()">\n <a ng-repeat="record in recordList" ng-href="{{generateEditUrl(record)}}">\n <div class="list-item">\n <div ng-class="css(\'span\',12/listSchema.length)" ng-repeat="field in listSchema">{{getListData(record, field.name)}} </div>\n </div>\n </a>\n </div>\n </div>\n</div>\n');
|
|
5411
|
+
$templateCache.put('base-view.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\n <div ng-hide="phase == \'ready\' && !spinning" class="loading">Loading…</div>\n <div ng-class="css(\'rowFluid\')" class="page-header edit-header">\n <div class="header-lhs col-sm-8 span8">\n <h1 id="header-text">{{modelNameDisplay}} :\n <span ng-repeat="field in listSchema" ng-bind-html="getListData(record, field.name) + \' \'"></span>\n </h1>\n </div>\n </div>\n <div class="container-fluid page-body edit-body">\n <form-input name="baseForm" schema="baseSchema()" formstyle="compact" viewform="true"></form-input>\n </div>\n</div>\n');
|
|
5412
|
+
$templateCache.put('error-display-bs2.html','<div id="display-error" ng-show="errorVisible" class="row-fluid ng-hide">\n <div class="alert alert-error offset1 span10">\n <button type="button" id="err-hide" class="close" ng-click="dismissError()"><i class="icon-remove"></i></button>\n <button type="button" id="err-pin" class="close" ng-click="stickError()"><i class="icon-eye-open"></i></button>\n <h4 id="err-title">{{alertTitle}}</h4>\n <div id="err-msg" ng-bind-html="errorMessage"></div>\n </div>\n</div>\n');
|
|
5413
|
+
$templateCache.put('error-display-bs3.html','<div id="display-error" ng-show="errorVisible" class="row ng-hide">\n <div class="alert alert-error col-md-offset-1 col-md-10 alert-danger">\n <button type="button" id="err-hide" class="close" ng-click="dismissError()"><i class="glyphicon glyphicon-remove"></i></button>\n <button type="button" id="err-pin" class="close" ng-click="stickError()"><i class="glyphicon glyphicon-pushpin"></i></button>\n <h4 id="err-title">{{alertTitle}}</h4>\n <div id="err-msg" ng-bind-html="errorMessage"></div>\n </div>\n</div>\n');
|
|
4013
5414
|
$templateCache.put('error-messages.html','<div ng-message="required">A value is required for this field</div>\n<div ng-message="minlength">Too few characters entered</div>\n<div ng-message="maxlength">Too many characters entered</div>\n<div ng-message="min">That value is too small</div>\n<div ng-message="max">That value is too large</div>\n<div ng-message="email">You need to enter a valid email address</div>\n<div ng-message="pattern">This field does not match the expected pattern</div>\n');
|
|
4014
|
-
$templateCache.put('form-button-bs2.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-mini btn-primary form-btn" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="icon-ok"></i> Save</button>\n <button id="cancelButton" class="btn btn-mini btn-warning form-btn" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="icon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-mini btn-success form-btn" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="icon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-mini btn-danger form-btn" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="icon-minus"></i> Delete</button>\n </div>\n</div>\n');
|
|
4015
|
-
$templateCache.put('form-button-bs3.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-primary form-btn btn-xs" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="glyphicon glyphicon-ok"></i> Save</button>\n <button id="cancelButton" class="btn btn-warning form-btn btn-xs" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="glyphicon glyphicon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-success form-btn btn-xs" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="glyphicon glyphicon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-danger form-btn btn-xs" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="glyphicon glyphicon-minus"></i> Delete</button>\n </div>\n</div>\n');
|
|
4016
|
-
$templateCache.put('search-bs2.html','<form class="navbar-search pull-right">\n <div id="search-cg" class="control-group" ng-class="errorClass">\n <input type="text" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <
|
|
4017
|
-
$templateCache.put('search-bs3.html','<form class="pull-right navbar-form">\n <div id="search-cg" class="form-group" ng-class="errorClass">\n <input type="text" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query form-control" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <
|
|
5415
|
+
$templateCache.put('form-button-bs2.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-mini btn-primary form-btn" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="icon-ok"></i> Save</button>\n <div id="why-disabled" ng-class="{showwhy:!!whyDisabled}" ng-bind-html="whyDisabled"></div>\n <button id="cancelButton" class="btn btn-mini btn-warning form-btn" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="icon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-mini btn-success form-btn" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="icon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-mini btn-danger form-btn" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="icon-minus"></i> Delete</button>\n </div>\n</div>\n');
|
|
5416
|
+
$templateCache.put('form-button-bs3.html','<div class="form-btn-grp">\n <div class="btn-group pull-right">\n <button id="saveButton" class="btn btn-primary form-btn btn-xs" ng-click="save()" ng-disabled="isSaveDisabled()"><i class="glyphicon glyphicon-ok"></i> Save</button>\n <div id="why-disabled" ng-class="{showwhy:!!whyDisabled}" ng-bind-html="whyDisabled"></div>\n <button id="cancelButton" class="btn btn-warning form-btn btn-xs" ng-click="cancel()" ng-disabled="isCancelDisabled()"><i class="glyphicon glyphicon-remove"></i> Cancel</button>\n </div>\n <div class="btn-group pull-right">\n <button id="newButton" class="btn btn-success form-btn btn-xs" ng-click="newClick()" ng-disabled="isNewDisabled()"><i class="glyphicon glyphicon-plus"></i> New</button>\n <button id="deleteButton" class="btn btn-danger form-btn btn-xs" ng-click="deleteClick()" ng-disabled="isDeleteDisabled()"><i class="glyphicon glyphicon-minus"></i> Delete</button>\n </div>\n</div>\n');
|
|
5417
|
+
$templateCache.put('search-bs2.html','<form class="navbar-search pull-right">\n <div id="search-cg" class="control-group" ng-class="errorClass">\n <input type="text" spellcheck="false" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <a href="{{result.href}}" ng-class="resultClass($index)" title="{{result.additional}}">{{result.resourceText}} {{result.text}}</a>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');
|
|
5418
|
+
$templateCache.put('search-bs3.html','<form class="pull-right navbar-form">\n <div id="search-cg" class="form-group" ng-class="errorClass">\n <input type="text" spellcheck="false" autocomplete="off" id="searchinput" ng-model="searchTarget" ng-model-options="{debounce:250}" class="search-query form-control" placeholder="{{searchPlaceholder}}" ng-keyup="handleKey($event)">\n </div>\n</form>\n<div class="results-container" ng-show="results.length >= 1">\n <div class="search-results">\n <div ng-repeat="result in results">\n <a href="{{result.href}}" ng-class="resultClass($index)" title="{{result.additional}}">{{result.resourceText}} {{result.text}}</a>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');}]);
|