forms-angular 0.12.0-beta.2 → 0.12.0-beta.200
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 +174 -4
- package/dist/client/forms-angular-bs2-specific.less +1 -1
- package/dist/client/forms-angular-bs3-specific.less +5 -2
- 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 +2344 -733
- package/dist/client/forms-angular.min.js +1 -1
- package/dist/client/index.d.ts +563 -92
- package/dist/server/data_form.js +1659 -953
- package/dist/server/index.d.ts +125 -0
- package/package.json +45 -44
- 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) {
|
|
@@ -59,37 +129,123 @@ var fng;
|
|
|
59
129
|
scope: { dataSrc: '&model' },
|
|
60
130
|
link: function (scope, element, attrs) {
|
|
61
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
|
+
}
|
|
87
198
|
}
|
|
88
|
-
|
|
89
|
-
|
|
199
|
+
else {
|
|
200
|
+
scope['link'] = undefined;
|
|
201
|
+
}
|
|
202
|
+
}, true);
|
|
203
|
+
}
|
|
204
|
+
else {
|
|
205
|
+
if (attrs['text'] && attrs['text'].length > 0) {
|
|
206
|
+
scope['text'] = attrs['text'];
|
|
207
|
+
}
|
|
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
|
}
|
|
@@ -112,14 +268,12 @@ var fng;
|
|
|
112
268
|
directives.formButtons = formButtons;
|
|
113
269
|
})(directives = fng.directives || (fng.directives = {}));
|
|
114
270
|
})(fng || (fng = {}));
|
|
115
|
-
/// <reference path="
|
|
116
|
-
/// <reference path="../../../../node_modules/@types/lodash/index.d.ts" />
|
|
117
|
-
/// <reference path="../fng-types" />
|
|
271
|
+
/// <reference path="../../index.d.ts" />
|
|
118
272
|
var fng;
|
|
119
273
|
(function (fng) {
|
|
120
274
|
var directives;
|
|
121
275
|
(function (directives) {
|
|
122
|
-
formInput.$inject = ["$compile", "$rootScope", "$filter", "$timeout", "cssFrameworkService", "formGenerator", "formMarkupHelper"];
|
|
276
|
+
formInput.$inject = ["$compile", "$rootScope", "$filter", "$timeout", "cssFrameworkService", "formGenerator", "formMarkupHelper", "securityService"];
|
|
123
277
|
var tabsSetupState;
|
|
124
278
|
(function (tabsSetupState) {
|
|
125
279
|
tabsSetupState[tabsSetupState["Y"] = 0] = "Y";
|
|
@@ -127,7 +281,7 @@ var fng;
|
|
|
127
281
|
tabsSetupState[tabsSetupState["Forced"] = 2] = "Forced";
|
|
128
282
|
})(tabsSetupState || (tabsSetupState = {}));
|
|
129
283
|
/*@ngInject*/
|
|
130
|
-
function formInput($compile, $rootScope, $filter, $timeout, cssFrameworkService, formGenerator, formMarkupHelper) {
|
|
284
|
+
function formInput($compile, $rootScope, $filter, $timeout, cssFrameworkService, formGenerator, formMarkupHelper, securityService) {
|
|
131
285
|
return {
|
|
132
286
|
restrict: 'EA',
|
|
133
287
|
link: function (scope, element, attrs) {
|
|
@@ -148,7 +302,7 @@ var fng;
|
|
|
148
302
|
// <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
|
|
149
303
|
// </div>
|
|
150
304
|
//
|
|
151
|
-
// Inline
|
|
305
|
+
// Inline or stacked
|
|
152
306
|
// <div class="form-group">
|
|
153
307
|
// <label class="sr-only" for="exampleInputEmail2">Email address</label>
|
|
154
308
|
// <input type="email" class="form-control" id="exampleInputEmail2" placeholder="Enter email">
|
|
@@ -167,11 +321,11 @@ var fng;
|
|
|
167
321
|
// <input type="text" placeholder="Type something…">
|
|
168
322
|
// <span class="help-block">Example block-level help text here.</span>
|
|
169
323
|
//
|
|
170
|
-
// Inline
|
|
324
|
+
// Inline or Stacked
|
|
171
325
|
// <input type="text" class="input-small" placeholder="Email">
|
|
172
326
|
var subkeys = [];
|
|
173
327
|
var tabsSetup = tabsSetupState.N;
|
|
174
|
-
var generateInput = function (fieldInfo, modelString, isRequired,
|
|
328
|
+
var generateInput = function (fieldInfo, modelString, isRequired, options) {
|
|
175
329
|
function generateEnumInstructions() {
|
|
176
330
|
var enumInstruction;
|
|
177
331
|
if (angular.isArray(scope[fieldInfo.options])) {
|
|
@@ -197,6 +351,10 @@ var fng;
|
|
|
197
351
|
}
|
|
198
352
|
return enumInstruction;
|
|
199
353
|
}
|
|
354
|
+
var idString = fieldInfo.id;
|
|
355
|
+
if (fieldInfo.array || options.subschema) {
|
|
356
|
+
idString = formMarkupHelper.generateArrayElementIdString(idString, fieldInfo, options);
|
|
357
|
+
}
|
|
200
358
|
var nameString;
|
|
201
359
|
if (!modelString) {
|
|
202
360
|
var modelBase = (options.model || 'record') + '.';
|
|
@@ -208,7 +366,7 @@ var fng;
|
|
|
208
366
|
var lastPart = compoundName.slice(root.length + 1);
|
|
209
367
|
if (options.index) {
|
|
210
368
|
modelString += root + '[' + options.index + '].' + lastPart;
|
|
211
|
-
idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[
|
|
369
|
+
idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[|]\.)/g, '-');
|
|
212
370
|
}
|
|
213
371
|
else {
|
|
214
372
|
modelString += root;
|
|
@@ -218,7 +376,6 @@ var fng;
|
|
|
218
376
|
}
|
|
219
377
|
else {
|
|
220
378
|
modelString += '[$index].' + lastPart;
|
|
221
|
-
idString = null;
|
|
222
379
|
nameString = compoundName.replace(/\./g, '-');
|
|
223
380
|
}
|
|
224
381
|
}
|
|
@@ -230,9 +387,8 @@ var fng;
|
|
|
230
387
|
var allInputsVars = formMarkupHelper.allInputsVars(scope, fieldInfo, options, modelString, idString, nameString);
|
|
231
388
|
var common = allInputsVars.common;
|
|
232
389
|
var value;
|
|
233
|
-
var requiredStr = (isRequired || fieldInfo.required) ? ' required' : '';
|
|
234
390
|
isRequired = isRequired || fieldInfo.required;
|
|
235
|
-
var requiredStr = isRequired ? ' required' : '';
|
|
391
|
+
var requiredStr = isRequired ? ' required ' : '';
|
|
236
392
|
var enumInstruction;
|
|
237
393
|
switch (fieldInfo.type) {
|
|
238
394
|
case 'select':
|
|
@@ -240,8 +396,9 @@ var fng;
|
|
|
240
396
|
value = '<input placeholder="fng-select2 has been removed" readonly>';
|
|
241
397
|
}
|
|
242
398
|
else {
|
|
243
|
-
common += (fieldInfo.
|
|
399
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
244
400
|
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
401
|
+
common += " aria-label=\"".concat(fieldInfo.label && fieldInfo.label !== "" ? fieldInfo.label : fieldInfo.name, "\" ");
|
|
245
402
|
value = '<select ' + common + 'class="' + allInputsVars.formControl.trim() + allInputsVars.compactClass + allInputsVars.sizeClassBS2 + '" ' + requiredStr + '>';
|
|
246
403
|
if (!isRequired) {
|
|
247
404
|
value += '<option></option>';
|
|
@@ -274,17 +431,28 @@ var fng;
|
|
|
274
431
|
if (fieldInfo.form) {
|
|
275
432
|
value += ' form="' + fieldInfo.form + '"';
|
|
276
433
|
}
|
|
277
|
-
if (fieldInfo.
|
|
278
|
-
value += '
|
|
434
|
+
if (fieldInfo.linktab) {
|
|
435
|
+
value += ' linktab="' + fieldInfo.linktab + '"';
|
|
436
|
+
}
|
|
437
|
+
if (fieldInfo.linktext) {
|
|
438
|
+
value += ' text="' + fieldInfo.linktext + '"';
|
|
279
439
|
}
|
|
280
440
|
if (fieldInfo.readonly) {
|
|
281
|
-
|
|
441
|
+
if (typeof fieldInfo.readonly === "boolean") {
|
|
442
|
+
value += " readonly=\"true\"";
|
|
443
|
+
}
|
|
444
|
+
else {
|
|
445
|
+
value += " ng-readonly=\"".concat(fieldInfo.readonly, "\"");
|
|
446
|
+
}
|
|
282
447
|
}
|
|
283
448
|
value += '></fng-link>';
|
|
284
449
|
break;
|
|
285
450
|
case 'radio':
|
|
286
451
|
value = '';
|
|
287
|
-
common += requiredStr
|
|
452
|
+
common += requiredStr;
|
|
453
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
454
|
+
;
|
|
455
|
+
common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
|
|
288
456
|
var separateLines = options.formstyle === 'vertical' || (options.formstyle !== 'inline' && !fieldInfo.inlineRadio);
|
|
289
457
|
if (angular.isArray(fieldInfo.options)) {
|
|
290
458
|
if (options.subschema) {
|
|
@@ -294,8 +462,7 @@ var fng;
|
|
|
294
462
|
var thisCommon_1;
|
|
295
463
|
angular.forEach(fieldInfo.options, function (optValue, idx) {
|
|
296
464
|
thisCommon_1 = common.replace('id="', 'id="' + idx + '-');
|
|
297
|
-
value +=
|
|
298
|
-
value += ' value="' + optValue + '">' + optValue;
|
|
465
|
+
value += "<input ".concat(thisCommon_1, " type=\"radio\" aria-label=\"").concat(optValue, "\" value=\"").concat(optValue, "\">").concat(optValue);
|
|
299
466
|
if (separateLines) {
|
|
300
467
|
value += '<br />';
|
|
301
468
|
}
|
|
@@ -310,22 +477,23 @@ var fng;
|
|
|
310
477
|
}
|
|
311
478
|
enumInstruction = generateEnumInstructions();
|
|
312
479
|
value += '<' + tagType + ' ng-repeat="option in ' + enumInstruction.repeat + '">';
|
|
313
|
-
value +=
|
|
314
|
-
value += enumInstruction.label || enumInstruction.value;
|
|
315
|
-
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, "> ");
|
|
316
481
|
}
|
|
317
482
|
break;
|
|
318
483
|
case 'checkbox':
|
|
319
|
-
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);
|
|
320
489
|
if (cssFrameworkService.framework() === 'bs3') {
|
|
321
|
-
value = '<div class="checkbox"
|
|
322
|
-
}
|
|
323
|
-
else {
|
|
324
|
-
value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
|
|
490
|
+
value = '<div class="checkbox">' + value + '</div>';
|
|
325
491
|
}
|
|
326
492
|
break;
|
|
327
493
|
default:
|
|
328
494
|
common += formMarkupHelper.addTextInputMarkup(allInputsVars, fieldInfo, requiredStr);
|
|
495
|
+
common += formMarkupHelper.handleReadOnlyDisabled(fieldInfo, scope).join(" ");
|
|
496
|
+
;
|
|
329
497
|
if (fieldInfo.type === 'textarea') {
|
|
330
498
|
if (fieldInfo.rows) {
|
|
331
499
|
if (fieldInfo.rows === 'auto') {
|
|
@@ -336,12 +504,13 @@ var fng;
|
|
|
336
504
|
}
|
|
337
505
|
}
|
|
338
506
|
if (fieldInfo.editor === 'ckEditor') {
|
|
507
|
+
console.log('Deprecation Warning: "editor" property deprecated - use "add"');
|
|
339
508
|
common += 'ckeditor = "" ';
|
|
340
509
|
if (cssFrameworkService.framework() === 'bs3') {
|
|
341
510
|
allInputsVars.sizeClassBS3 = 'col-xs-12';
|
|
342
511
|
}
|
|
343
512
|
}
|
|
344
|
-
value = '<textarea ' + common + '
|
|
513
|
+
value = '<textarea ' + common + '></textarea>';
|
|
345
514
|
}
|
|
346
515
|
else {
|
|
347
516
|
value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
|
|
@@ -361,6 +530,9 @@ var fng;
|
|
|
361
530
|
case 'inline':
|
|
362
531
|
result = 'form-inline';
|
|
363
532
|
break;
|
|
533
|
+
case 'stacked':
|
|
534
|
+
result = 'form-stacked';
|
|
535
|
+
break;
|
|
364
536
|
case 'horizontalCompact':
|
|
365
537
|
result = 'form-horizontal compact';
|
|
366
538
|
break;
|
|
@@ -371,11 +543,12 @@ var fng;
|
|
|
371
543
|
return result;
|
|
372
544
|
};
|
|
373
545
|
var containerInstructions = function (info) {
|
|
374
|
-
var result
|
|
546
|
+
var result;
|
|
375
547
|
if (typeof info.containerType === 'function') {
|
|
376
548
|
result = info.containerType(info);
|
|
377
549
|
}
|
|
378
550
|
else {
|
|
551
|
+
result = {};
|
|
379
552
|
switch (info.containerType) {
|
|
380
553
|
case 'tab':
|
|
381
554
|
var tabNo = -1;
|
|
@@ -388,12 +561,26 @@ var fng;
|
|
|
388
561
|
if (tabNo >= 0) {
|
|
389
562
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
390
563
|
// result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"'
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
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>';
|
|
394
583
|
}
|
|
395
|
-
result.before += '>';
|
|
396
|
-
result.after = '</uib-tab>';
|
|
397
584
|
}
|
|
398
585
|
else {
|
|
399
586
|
result.before = '<p>Error! Tab ' + info.title + ' not found in tab list</p>';
|
|
@@ -449,8 +636,46 @@ var fng;
|
|
|
449
636
|
}
|
|
450
637
|
return result;
|
|
451
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
|
+
};
|
|
452
674
|
var handleField = function (info, options) {
|
|
453
675
|
var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
|
|
676
|
+
if (fieldChrome.omit) {
|
|
677
|
+
return "";
|
|
678
|
+
}
|
|
454
679
|
var template = fieldChrome.template;
|
|
455
680
|
if (info.schema) {
|
|
456
681
|
var niceName = info.name.replace(/\./g, '_');
|
|
@@ -471,7 +696,8 @@ var fng;
|
|
|
471
696
|
formstyle: options.formstyle,
|
|
472
697
|
subkey: schemaDefName + '_subkey',
|
|
473
698
|
subkeyno: arraySel,
|
|
474
|
-
subschemaroot: info.name
|
|
699
|
+
subschemaroot: info.name,
|
|
700
|
+
suppressNestingWarning: info.suppressNestingWarning
|
|
475
701
|
});
|
|
476
702
|
template += topAndTail.after;
|
|
477
703
|
}
|
|
@@ -479,7 +705,9 @@ var fng;
|
|
|
479
705
|
}
|
|
480
706
|
else {
|
|
481
707
|
if (options.subschema) {
|
|
482
|
-
|
|
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
|
+
}
|
|
483
711
|
}
|
|
484
712
|
else {
|
|
485
713
|
var model = (options.model || 'record') + '.' + info.name;
|
|
@@ -494,27 +722,54 @@ var fng;
|
|
|
494
722
|
'<i class="' + formMarkupHelper.glyphClass() + '-plus"></i> Add</button>';
|
|
495
723
|
}
|
|
496
724
|
if (cssFrameworkService.framework() === 'bs3') {
|
|
497
|
-
template += '<div class="row"><div class="
|
|
725
|
+
template += '<div class="row schema-head"><div class="col-sm-offset-3">' + info.label + topButton + '</div></div>';
|
|
498
726
|
}
|
|
499
727
|
else {
|
|
500
728
|
template += '<div class="schema-head">' + info.label + topButton + '</div>';
|
|
501
729
|
}
|
|
502
730
|
}
|
|
503
731
|
/* Array body */
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
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
|
+
">";
|
|
508
760
|
if (cssFrameworkService.framework() === 'bs2') {
|
|
509
761
|
template += '<div class="row-fluid sub-doc">';
|
|
510
762
|
}
|
|
511
|
-
if (
|
|
512
|
-
|
|
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 : "", ">");
|
|
513
768
|
if (typeof info.customSubDoc == 'string') {
|
|
514
769
|
template += info.customSubDoc;
|
|
515
770
|
}
|
|
516
771
|
if (info.noRemove !== true) {
|
|
517
|
-
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)\"");
|
|
518
773
|
if (info.remove) {
|
|
519
774
|
template += ' class="remove-btn btn btn-mini btn-default btn-xs form-btn"><i class="' + formMarkupHelper.glyphClass() + '-minus"></i> Remove';
|
|
520
775
|
}
|
|
@@ -531,31 +786,69 @@ var fng;
|
|
|
531
786
|
}
|
|
532
787
|
template += '</div> ';
|
|
533
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
|
+
}
|
|
534
798
|
template += processInstructions(info.schema, false, {
|
|
535
799
|
subschema: 'true',
|
|
536
800
|
formstyle: info.formStyle,
|
|
537
801
|
model: options.model,
|
|
538
|
-
subschemaroot: info.name
|
|
802
|
+
subschemaroot: info.name,
|
|
803
|
+
suppressNestingWarning: info.suppressNestingWarning
|
|
539
804
|
});
|
|
805
|
+
if (parts === null || parts === void 0 ? void 0 : parts.after) {
|
|
806
|
+
template += parts.after;
|
|
807
|
+
}
|
|
540
808
|
if (cssFrameworkService.framework() === 'bs2') {
|
|
541
809
|
template += ' </div>';
|
|
542
810
|
}
|
|
543
811
|
template += '</li>';
|
|
544
812
|
template += '</ol>';
|
|
545
813
|
/* Array footer */
|
|
546
|
-
if (info.noAdd !== true || typeof info.customFooter == 'string') {
|
|
814
|
+
if (info.noAdd !== true || typeof info.customFooter == 'string' || info.noneIndicator) {
|
|
547
815
|
var footer = '';
|
|
548
816
|
if (typeof info.customFooter == 'string') {
|
|
549
817
|
footer = info.customFooter;
|
|
550
818
|
}
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
template += '<div class="row"><div class="schema-foot 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, "\"");
|
|
556
823
|
}
|
|
557
824
|
else {
|
|
558
|
-
|
|
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
|
+
}
|
|
559
852
|
}
|
|
560
853
|
}
|
|
561
854
|
}
|
|
@@ -567,34 +860,40 @@ var fng;
|
|
|
567
860
|
var controlDivClasses = formMarkupHelper.controlDivClasses(options);
|
|
568
861
|
if (info.array) {
|
|
569
862
|
controlDivClasses.push('fng-array');
|
|
570
|
-
if (options.formstyle === 'inline') {
|
|
571
|
-
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');
|
|
572
865
|
}
|
|
573
866
|
template += formMarkupHelper.label(scope, info, info.type !== 'link', options);
|
|
574
|
-
|
|
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);
|
|
575
872
|
}
|
|
576
873
|
else {
|
|
577
874
|
// Single fields here
|
|
578
875
|
template += formMarkupHelper.label(scope, info, null, options);
|
|
579
|
-
|
|
580
|
-
console.log("********* Options required - found it ********");
|
|
581
|
-
}
|
|
582
|
-
template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, info.id, options), controlDivClasses);
|
|
876
|
+
template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, options), controlDivClasses);
|
|
583
877
|
}
|
|
584
878
|
}
|
|
585
879
|
template += fieldChrome.closeTag;
|
|
586
880
|
return template;
|
|
587
881
|
};
|
|
588
|
-
var inferMissingProperties = function (info) {
|
|
882
|
+
var inferMissingProperties = function (info, options) {
|
|
589
883
|
// infer missing values
|
|
590
884
|
info.type = info.type || 'text';
|
|
591
885
|
if (info.id) {
|
|
592
|
-
if (typeof info.id === 'number' ||
|
|
886
|
+
if (typeof info.id === 'number' || info.id.match(/^[0-9]/)) {
|
|
593
887
|
info.id = '_' + info.id;
|
|
594
888
|
}
|
|
595
889
|
}
|
|
596
890
|
else {
|
|
597
|
-
|
|
891
|
+
if (options && options.noid) {
|
|
892
|
+
info.id = null;
|
|
893
|
+
}
|
|
894
|
+
else {
|
|
895
|
+
info.id = 'f_' + info.name.replace(/\./g, '_');
|
|
896
|
+
}
|
|
598
897
|
}
|
|
599
898
|
info.label = (info.label !== undefined) ? (info.label === null ? '' : info.label) : $filter('titleCase')(info.name.split('.').slice(-1)[0]);
|
|
600
899
|
};
|
|
@@ -605,7 +904,11 @@ var fng;
|
|
|
605
904
|
if (instructionsArray) {
|
|
606
905
|
for (var anInstruction = 0; anInstruction < instructionsArray.length; anInstruction++) {
|
|
607
906
|
var info = instructionsArray[anInstruction];
|
|
608
|
-
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') {
|
|
609
912
|
info.add = info.add ? ' ' + info.add + ' ' : '';
|
|
610
913
|
if (info.add.indexOf('ui-date') === -1 && !options.noautofocus && !info.containerType) {
|
|
611
914
|
info.add = info.add + 'autofocus ';
|
|
@@ -614,9 +917,10 @@ var fng;
|
|
|
614
917
|
var callHandleField = true;
|
|
615
918
|
if (info.directive) {
|
|
616
919
|
var directiveName = info.directive;
|
|
617
|
-
var newElement =
|
|
920
|
+
var newElement = info.customHeader || "";
|
|
921
|
+
newElement += '<' + directiveName + ' model="' + (options.model || 'record') + '"';
|
|
618
922
|
var thisElement = element[0];
|
|
619
|
-
inferMissingProperties(info);
|
|
923
|
+
inferMissingProperties(info, options);
|
|
620
924
|
for (var i = 0; i < thisElement.attributes.length; i++) {
|
|
621
925
|
var thisAttr = thisElement.attributes[i];
|
|
622
926
|
switch (thisAttr.nodeName) {
|
|
@@ -652,7 +956,9 @@ var fng;
|
|
|
652
956
|
break;
|
|
653
957
|
case 'object':
|
|
654
958
|
for (var subAdd in info.add) {
|
|
655
|
-
|
|
959
|
+
if (info.add.hasOwnProperty(subAdd)) {
|
|
960
|
+
newElement += ' ' + subAdd + '="' + info.add[subAdd].toString().replace(/"/g, '"') + '"';
|
|
961
|
+
}
|
|
656
962
|
}
|
|
657
963
|
break;
|
|
658
964
|
default:
|
|
@@ -661,12 +967,25 @@ var fng;
|
|
|
661
967
|
break;
|
|
662
968
|
case directiveCamel:
|
|
663
969
|
for (var subProp in info[prop]) {
|
|
664
|
-
|
|
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
|
+
}
|
|
665
979
|
}
|
|
666
980
|
break;
|
|
667
981
|
default:
|
|
668
982
|
if (info[prop]) {
|
|
669
|
-
|
|
983
|
+
if (typeof info[prop] === 'string') {
|
|
984
|
+
newElement += ' fng-fld-' + prop + '="' + info[prop].replace(/"/g, '"') + '"';
|
|
985
|
+
}
|
|
986
|
+
else {
|
|
987
|
+
newElement += ' fng-fld-' + prop + '="' + JSON.stringify(info[prop]).replace(/"/g, '"') + '"';
|
|
988
|
+
}
|
|
670
989
|
}
|
|
671
990
|
break;
|
|
672
991
|
}
|
|
@@ -678,10 +997,13 @@ var fng;
|
|
|
678
997
|
}
|
|
679
998
|
}
|
|
680
999
|
newElement += 'ng-model="' + info.name + '"></' + directiveName + '>';
|
|
1000
|
+
newElement += (info.customFooter || "");
|
|
681
1001
|
result += newElement;
|
|
682
1002
|
callHandleField = false;
|
|
683
1003
|
}
|
|
684
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...
|
|
685
1007
|
var parts = containerInstructions(info);
|
|
686
1008
|
switch (info.containerType) {
|
|
687
1009
|
case 'tab':
|
|
@@ -692,9 +1014,12 @@ var fng;
|
|
|
692
1014
|
var activeTabNo = _.findIndex(scope.tabs, function (tab) { return (tab.active); });
|
|
693
1015
|
scope.activeTabNo = activeTabNo >= 0 ? activeTabNo : 0;
|
|
694
1016
|
}
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
+
}
|
|
698
1023
|
break;
|
|
699
1024
|
case 'tabset':
|
|
700
1025
|
tabsSetup = tabsSetupState.Y;
|
|
@@ -714,9 +1039,7 @@ var fng;
|
|
|
714
1039
|
else if (options.subkey) {
|
|
715
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)
|
|
716
1041
|
var objectToSearch = angular.isArray(scope[options.subkey]) ? scope[options.subkey][0].keyList : scope[options.subkey].keyList;
|
|
717
|
-
if (_.find(objectToSearch, function (value, key) {
|
|
718
|
-
return scope[options.subkey].path + '.' + key === info.name;
|
|
719
|
-
})) {
|
|
1042
|
+
if (_.find(objectToSearch, function (value, key) { return scope[options.subkey].path + '.' + key === info.name; })) {
|
|
720
1043
|
callHandleField = false;
|
|
721
1044
|
}
|
|
722
1045
|
}
|
|
@@ -724,7 +1047,7 @@ var fng;
|
|
|
724
1047
|
// if (groupId) {
|
|
725
1048
|
// scope['showHide' + groupId] = true;
|
|
726
1049
|
// }
|
|
727
|
-
inferMissingProperties(info);
|
|
1050
|
+
inferMissingProperties(info, options);
|
|
728
1051
|
result += handleField(info, options);
|
|
729
1052
|
}
|
|
730
1053
|
}
|
|
@@ -738,8 +1061,9 @@ var fng;
|
|
|
738
1061
|
var unwatch = scope.$watch(attrs.schema, function (newValue) {
|
|
739
1062
|
if (newValue) {
|
|
740
1063
|
var newArrayValue = angular.isArray(newValue) ? newValue : [newValue]; // otherwise some old tests stop working for no real reason
|
|
741
|
-
if (newArrayValue.length > 0) {
|
|
1064
|
+
if (newArrayValue.length > 0 && typeof unwatch === "function") {
|
|
742
1065
|
unwatch();
|
|
1066
|
+
unwatch = null;
|
|
743
1067
|
var elementHtml = '';
|
|
744
1068
|
var recordAttribute = attrs.model || 'record'; // By default data comes from scope.record
|
|
745
1069
|
var theRecord = scope[recordAttribute];
|
|
@@ -753,12 +1077,13 @@ var fng;
|
|
|
753
1077
|
var customAttrs = '';
|
|
754
1078
|
for (var thisAttr in attrs) {
|
|
755
1079
|
if (attrs.hasOwnProperty(thisAttr)) {
|
|
756
|
-
if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model'].indexOf(thisAttr) === -1) {
|
|
1080
|
+
if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model', 'viewform'].indexOf(thisAttr) === -1) {
|
|
757
1081
|
customAttrs += ' ' + attrs.$attr[thisAttr] + '="' + attrs[thisAttr] + '"';
|
|
758
1082
|
}
|
|
759
1083
|
}
|
|
760
1084
|
}
|
|
761
|
-
|
|
1085
|
+
var tag = attrs.forceform ? 'ng-form' : 'form';
|
|
1086
|
+
elementHtml = "<".concat(tag, " name=\"").concat(scope.topLevelFormName, "\" class=\"").concat(convertFormStyleToClass(attrs.formstyle), "\" novalidate ").concat(customAttrs, ">");
|
|
762
1087
|
}
|
|
763
1088
|
if (theRecord === scope.topLevelFormName) {
|
|
764
1089
|
throw new Error('Model and Name must be distinct - they are both ' + theRecord);
|
|
@@ -774,10 +1099,12 @@ var fng;
|
|
|
774
1099
|
// If we have modelControllers we need to let them know when we have form + data
|
|
775
1100
|
var sharedData = scope[attrs.shared || 'sharedData'];
|
|
776
1101
|
var modelControllers_1 = sharedData ? sharedData.modelControllers : [];
|
|
777
|
-
if (subkeys.length > 0 || modelControllers_1.length > 0) {
|
|
1102
|
+
if ((subkeys.length > 0 || modelControllers_1.length > 0) && !scope.phaseWatcher) {
|
|
778
1103
|
var unwatch2 = scope.$watch('phase', function (newValue) {
|
|
779
|
-
|
|
1104
|
+
scope.phaseWatcher = true;
|
|
1105
|
+
if (newValue === 'ready' && typeof unwatch2 === "function") {
|
|
780
1106
|
unwatch2();
|
|
1107
|
+
unwatch2 = null;
|
|
781
1108
|
// Tell the 'model controllers' that the form and data are there
|
|
782
1109
|
for (var i = 0; i < modelControllers_1.length; i++) {
|
|
783
1110
|
if (modelControllers_1[i].onAllReady) {
|
|
@@ -806,7 +1133,7 @@ var fng;
|
|
|
806
1133
|
arrayOffset = scope[arrayToProcess[thisOffset].selectFunc](theRecord, info);
|
|
807
1134
|
}
|
|
808
1135
|
else if (arrayToProcess[thisOffset].keyList) {
|
|
809
|
-
// We are
|
|
1136
|
+
// We are choosing the array element by matching one or more keys
|
|
810
1137
|
var thisSubkeyList = arrayToProcess[thisOffset].keyList;
|
|
811
1138
|
for (arrayOffset = 0; arrayOffset < dataVal.length; arrayOffset++) {
|
|
812
1139
|
matching = true;
|
|
@@ -838,7 +1165,15 @@ var fng;
|
|
|
838
1165
|
break;
|
|
839
1166
|
case 'create':
|
|
840
1167
|
default:
|
|
841
|
-
|
|
1168
|
+
var nameElements = info.name.split('.');
|
|
1169
|
+
var lastPart = nameElements.pop();
|
|
1170
|
+
var possibleArray = nameElements.join('.');
|
|
1171
|
+
var obj = theRecord;
|
|
1172
|
+
// Should loop here when / if we re-introduce nesting
|
|
1173
|
+
if (possibleArray) {
|
|
1174
|
+
obj = obj[possibleArray];
|
|
1175
|
+
}
|
|
1176
|
+
arrayOffset = obj[lastPart].push(thisSubkeyList) - 1;
|
|
842
1177
|
break;
|
|
843
1178
|
}
|
|
844
1179
|
}
|
|
@@ -852,7 +1187,7 @@ var fng;
|
|
|
852
1187
|
}
|
|
853
1188
|
});
|
|
854
1189
|
}
|
|
855
|
-
$rootScope.$broadcast('formInputDone');
|
|
1190
|
+
$rootScope.$broadcast('formInputDone', attrs.name);
|
|
856
1191
|
if (formGenerator.updateDataDependentDisplay && theRecord && Object.keys(theRecord).length > 0) {
|
|
857
1192
|
// If this is not a test force the data dependent updates to the DOM
|
|
858
1193
|
formGenerator.updateDataDependentDisplay(theRecord, null, true, scope);
|
|
@@ -901,7 +1236,7 @@ var fng;
|
|
|
901
1236
|
/*@ngInject*/
|
|
902
1237
|
SearchCtrl.$inject = ["$scope", "$http", "$location", "routingService"];
|
|
903
1238
|
function SearchCtrl($scope, $http, $location, routingService) {
|
|
904
|
-
var
|
|
1239
|
+
var lastSearchSent;
|
|
905
1240
|
var _isNotMobile;
|
|
906
1241
|
_isNotMobile = (function () {
|
|
907
1242
|
var check = false;
|
|
@@ -941,7 +1276,7 @@ var fng;
|
|
|
941
1276
|
break;
|
|
942
1277
|
case 13:
|
|
943
1278
|
if ($scope.focus != null) {
|
|
944
|
-
$
|
|
1279
|
+
$location.url(makeUrlNoHtml5Hash($scope.results[$scope.focus]));
|
|
945
1280
|
}
|
|
946
1281
|
break;
|
|
947
1282
|
}
|
|
@@ -954,14 +1289,6 @@ var fng;
|
|
|
954
1289
|
$scope.results[index].focussed = true;
|
|
955
1290
|
$scope.focus = index;
|
|
956
1291
|
};
|
|
957
|
-
$scope.selectResult = function (resultNo) {
|
|
958
|
-
var result = $scope.results[resultNo];
|
|
959
|
-
var newURL = routingService.prefix() + '/' + result.resource + '/' + result.id + '/edit';
|
|
960
|
-
if (result.resourceTab) {
|
|
961
|
-
newURL += '/' + result.resourceTab;
|
|
962
|
-
}
|
|
963
|
-
$location.url(newURL);
|
|
964
|
-
};
|
|
965
1292
|
$scope.resultClass = function (index) {
|
|
966
1293
|
var resultClass = 'search-result';
|
|
967
1294
|
if ($scope.results && $scope.results[index].focussed) {
|
|
@@ -975,16 +1302,23 @@ var fng;
|
|
|
975
1302
|
$scope.results = [];
|
|
976
1303
|
$scope.focus = null;
|
|
977
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
|
+
}
|
|
978
1309
|
$scope.$watch('searchTarget', function (newValue) {
|
|
979
1310
|
if (newValue && newValue.length > 0) {
|
|
980
|
-
|
|
981
|
-
$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) {
|
|
982
1313
|
var data = response.data;
|
|
983
1314
|
// Check that we haven't fired off a subsequent request, in which
|
|
984
1315
|
// case we are no longer interested in these results
|
|
985
|
-
if (
|
|
1316
|
+
if (!data.timestamps || !data.timestamps.sentAt || Number.parseInt(data.timestamps.sentAt) === lastSearchSent) {
|
|
986
1317
|
if ($scope.searchTarget.length > 0) {
|
|
987
1318
|
$scope.results = data.results;
|
|
1319
|
+
$scope.results.forEach(function (result) {
|
|
1320
|
+
result.href = routingService.html5hash() + makeUrlNoHtml5Hash(result);
|
|
1321
|
+
});
|
|
988
1322
|
$scope.moreCount = data.moreCount;
|
|
989
1323
|
if (data.results.length > 0) {
|
|
990
1324
|
$scope.errorClass = '';
|
|
@@ -1049,6 +1383,21 @@ var fng;
|
|
|
1049
1383
|
})(fng || (fng = {}));
|
|
1050
1384
|
/// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
|
|
1051
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;
|
|
1052
1401
|
(function (fng) {
|
|
1053
1402
|
var filters;
|
|
1054
1403
|
(function (filters) {
|
|
@@ -1216,8 +1565,7 @@ var fng;
|
|
|
1216
1565
|
services.cssFrameworkService = cssFrameworkService;
|
|
1217
1566
|
})(services = fng.services || (fng.services = {}));
|
|
1218
1567
|
})(fng || (fng = {}));
|
|
1219
|
-
/// <reference path="
|
|
1220
|
-
/// <reference path="../fng-types" />
|
|
1568
|
+
/// <reference path="../../index.d.ts" />
|
|
1221
1569
|
var fng;
|
|
1222
1570
|
(function (fng) {
|
|
1223
1571
|
var services;
|
|
@@ -1230,6 +1578,7 @@ var fng;
|
|
|
1230
1578
|
controllerName += 'Ctrl';
|
|
1231
1579
|
locals.$scope = sharedData.modelControllers[level] = localScope;
|
|
1232
1580
|
var parentScope = localScope.$parent;
|
|
1581
|
+
parentScope.items = parentScope.items || [];
|
|
1233
1582
|
var addMenuOptions = function (array) {
|
|
1234
1583
|
angular.forEach(array, function (value) {
|
|
1235
1584
|
if (value.divider) {
|
|
@@ -1240,6 +1589,10 @@ var fng;
|
|
|
1240
1589
|
needDivider = false;
|
|
1241
1590
|
parentScope.items.push({ divider: true });
|
|
1242
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
|
+
}
|
|
1243
1596
|
parentScope.items.push(value);
|
|
1244
1597
|
}
|
|
1245
1598
|
});
|
|
@@ -1261,6 +1614,11 @@ var fng;
|
|
|
1261
1614
|
locals.$scope.contextMenuPromise.then(function (array) { return addMenuOptions(array); });
|
|
1262
1615
|
}
|
|
1263
1616
|
}
|
|
1617
|
+
if (sharedData.modelNameDisplayPromise) {
|
|
1618
|
+
sharedData.modelNameDisplayPromise.then(function (value) {
|
|
1619
|
+
parentScope.modelNameDisplay = value;
|
|
1620
|
+
});
|
|
1621
|
+
}
|
|
1264
1622
|
}
|
|
1265
1623
|
catch (error) {
|
|
1266
1624
|
// Check to see if error is no such controller - don't care
|
|
@@ -1275,8 +1633,7 @@ var fng;
|
|
|
1275
1633
|
fngModelCtrlService.$inject = ["$controller"];
|
|
1276
1634
|
})(services = fng.services || (fng.services = {}));
|
|
1277
1635
|
})(fng || (fng = {}));
|
|
1278
|
-
/// <reference path="
|
|
1279
|
-
/// <reference path="../fng-types" />
|
|
1636
|
+
/// <reference path="../../index.d.ts" />
|
|
1280
1637
|
var fng;
|
|
1281
1638
|
(function (fng) {
|
|
1282
1639
|
var services;
|
|
@@ -1291,7 +1648,7 @@ var fng;
|
|
|
1291
1648
|
routing: 'ngroute',
|
|
1292
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)
|
|
1293
1650
|
};
|
|
1294
|
-
var postActions = ['edit'];
|
|
1651
|
+
var postActions = ['edit', 'view'];
|
|
1295
1652
|
var builtInRoutes = [
|
|
1296
1653
|
{
|
|
1297
1654
|
route: '/analyse/:model/:reportSchemaName',
|
|
@@ -1301,12 +1658,19 @@ var fng;
|
|
|
1301
1658
|
{ route: '/analyse/:model', state: 'analyse::model', templateUrl: 'base-analysis.html' },
|
|
1302
1659
|
{ route: '/:model/:id/edit', state: 'model::edit', templateUrl: 'base-edit.html' },
|
|
1303
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' },
|
|
1304
1663
|
{ route: '/:model/new', state: 'model::new', templateUrl: 'base-edit.html' },
|
|
1305
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)
|
|
1306
1667
|
{ route: '/:model/:form/:id/edit', state: 'model::form::edit', templateUrl: 'base-edit.html' },
|
|
1307
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' },
|
|
1308
1671
|
{ route: '/:model/:form/new', state: 'model::form::new', templateUrl: 'base-edit.html' },
|
|
1309
|
-
{ 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
|
|
1310
1674
|
];
|
|
1311
1675
|
var _routeProvider, _stateProvider;
|
|
1312
1676
|
var lastRoute = null;
|
|
@@ -1360,6 +1724,12 @@ var fng;
|
|
|
1360
1724
|
case 'edit':
|
|
1361
1725
|
urlStr = modelString + formString + '/' + id + '/edit' + tabString;
|
|
1362
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;
|
|
1363
1733
|
case 'new':
|
|
1364
1734
|
urlStr = modelString + formString + '/new' + tabString;
|
|
1365
1735
|
break;
|
|
@@ -1442,8 +1812,9 @@ var fng;
|
|
|
1442
1812
|
lastObject.modelName = locationSplit[1];
|
|
1443
1813
|
var lastParts_1 = [locationSplit[locationParts - 1], locationSplit[locationParts - 2]];
|
|
1444
1814
|
var newPos = lastParts_1.indexOf('new');
|
|
1815
|
+
var viewonlyPos = lastParts_1.indexOf('viewonly');
|
|
1445
1816
|
var actionPos = void 0;
|
|
1446
|
-
if (newPos === -1) {
|
|
1817
|
+
if (newPos === -1 && viewonlyPos === -1) {
|
|
1447
1818
|
actionPos = postActions.reduce(function (previousValue, currentValue) {
|
|
1448
1819
|
var pos = lastParts_1.indexOf(currentValue);
|
|
1449
1820
|
return pos > -1 ? pos : previousValue;
|
|
@@ -1453,10 +1824,13 @@ var fng;
|
|
|
1453
1824
|
lastObject.id = locationSplit[locationParts];
|
|
1454
1825
|
}
|
|
1455
1826
|
}
|
|
1456
|
-
else {
|
|
1827
|
+
else if (newPos !== -1) {
|
|
1457
1828
|
lastObject.newRecord = true;
|
|
1458
1829
|
locationParts -= (1 + newPos);
|
|
1459
1830
|
}
|
|
1831
|
+
else {
|
|
1832
|
+
locationParts -= (1 + viewonlyPos);
|
|
1833
|
+
}
|
|
1460
1834
|
if (actionPos === 1 || newPos === 1) {
|
|
1461
1835
|
lastObject.tab = lastParts_1[0];
|
|
1462
1836
|
}
|
|
@@ -1467,34 +1841,9 @@ var fng;
|
|
|
1467
1841
|
}
|
|
1468
1842
|
return lastObject;
|
|
1469
1843
|
};
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
// * Parser for the states provided by ui.router
|
|
1474
|
-
// */
|
|
1475
|
-
//'use strict';
|
|
1476
|
-
//formsAngular.factory('$stateParse', [function () {
|
|
1477
|
-
//
|
|
1478
|
-
// var lastObject = {};
|
|
1479
|
-
//
|
|
1480
|
-
// return function (state) {
|
|
1481
|
-
// if (state.current && state.current.name) {
|
|
1482
|
-
// lastObject = {newRecord: false};
|
|
1483
|
-
// lastObject.modelName = state.params.model;
|
|
1484
|
-
// if (state.current.name === 'model::list') {
|
|
1485
|
-
// lastObject = {index: true};
|
|
1486
|
-
// lastObject.modelName = state.params.model;
|
|
1487
|
-
// } else if (state.current.name === 'model::edit') {
|
|
1488
|
-
// lastObject.id = state.params.id;
|
|
1489
|
-
// } else if (state.current.name === 'model::new') {
|
|
1490
|
-
// lastObject.newRecord = true;
|
|
1491
|
-
// } else if (state.current.name === 'model::analyse') {
|
|
1492
|
-
// lastObject.analyse = true;
|
|
1493
|
-
// }
|
|
1494
|
-
// }
|
|
1495
|
-
// return lastObject;
|
|
1496
|
-
// };
|
|
1497
|
-
//}]);
|
|
1844
|
+
},
|
|
1845
|
+
html5hash: function () {
|
|
1846
|
+
return config.html5Mode ? '' : '#';
|
|
1498
1847
|
},
|
|
1499
1848
|
buildUrl: function (path) {
|
|
1500
1849
|
var url = config.html5Mode ? '' : '#';
|
|
@@ -1511,46 +1860,25 @@ var fng;
|
|
|
1511
1860
|
},
|
|
1512
1861
|
redirectTo: function () {
|
|
1513
1862
|
return function (operation, scope, location, id, tab) {
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
if (
|
|
1517
|
-
|
|
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);
|
|
1518
1880
|
}
|
|
1519
|
-
var urlStr = _buildOperationUrl(config.prefix, operation, scope.modelName, scope.formName, id, tab);
|
|
1520
1881
|
location.path(urlStr);
|
|
1521
|
-
// break;
|
|
1522
|
-
// case 'uirouter' :
|
|
1523
|
-
// var formString = scope.formName ? ('/' + scope.formName) : '';
|
|
1524
|
-
// var modelString = config.prefix + '/' + scope.modelName;
|
|
1525
|
-
// console.log('form schemas not supported with ui-router');
|
|
1526
|
-
// switch (operation) {
|
|
1527
|
-
// case 'list' :
|
|
1528
|
-
// location.path(modelString + formString);
|
|
1529
|
-
// break;
|
|
1530
|
-
// case 'edit' :
|
|
1531
|
-
// location.path(modelString + formString + '/' + id + '/edit');
|
|
1532
|
-
// break;
|
|
1533
|
-
// case 'new' :
|
|
1534
|
-
// location.path(modelString + formString + '/new');
|
|
1535
|
-
// break;
|
|
1536
|
-
// }
|
|
1537
|
-
// switch (operation) {
|
|
1538
|
-
// case 'list' :
|
|
1539
|
-
// $state.go('model::list', { model: model });
|
|
1540
|
-
// break;
|
|
1541
|
-
// case 'edit' :
|
|
1542
|
-
// location.path('/' + scope.modelName + formString + '/' + id + '/edit');
|
|
1543
|
-
// break;
|
|
1544
|
-
// case 'new' :
|
|
1545
|
-
// location.path('/' + scope.modelName + formString + '/new');
|
|
1546
|
-
// break;
|
|
1547
|
-
// }
|
|
1548
|
-
// break;
|
|
1549
|
-
//
|
|
1550
|
-
//
|
|
1551
|
-
// // edit: $state.go('model::edit', {id: data._id, model: $scope.modelName });
|
|
1552
|
-
// // new: $state.go('model::new', {model: $scope.modelName});
|
|
1553
|
-
// break;
|
|
1554
1882
|
};
|
|
1555
1883
|
}
|
|
1556
1884
|
};
|
|
@@ -1560,7 +1888,7 @@ var fng;
|
|
|
1560
1888
|
services.routingService = routingService;
|
|
1561
1889
|
})(services = fng.services || (fng.services = {}));
|
|
1562
1890
|
})(fng || (fng = {}));
|
|
1563
|
-
/// <reference path="
|
|
1891
|
+
/// <reference path="../../index.d.ts" />
|
|
1564
1892
|
var fng;
|
|
1565
1893
|
(function (fng) {
|
|
1566
1894
|
var services;
|
|
@@ -1572,7 +1900,7 @@ var fng;
|
|
|
1572
1900
|
* All methods should be state-less
|
|
1573
1901
|
*
|
|
1574
1902
|
*/
|
|
1575
|
-
function formGenerator($
|
|
1903
|
+
function formGenerator($filter, routingService, recordHandler, securityService) {
|
|
1576
1904
|
function handleSchema(description, source, destForm, destList, prefix, doRecursion, $scope, ctrlState) {
|
|
1577
1905
|
function handletabInfo(tabName, thisInst) {
|
|
1578
1906
|
var tabTitle = angular.copy(tabName);
|
|
@@ -1598,50 +1926,55 @@ var fng;
|
|
|
1598
1926
|
}
|
|
1599
1927
|
tab.content.push(thisInst);
|
|
1600
1928
|
}
|
|
1929
|
+
if (typeof $scope.onSchemaFetch === "function") {
|
|
1930
|
+
$scope.onSchemaFetch(description, source);
|
|
1931
|
+
}
|
|
1601
1932
|
for (var field in source) {
|
|
1602
|
-
if (field
|
|
1603
|
-
if (
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
}
|
|
1607
|
-
else if (source.hasOwnProperty(field)) {
|
|
1608
|
-
var mongooseType = source[field], mongooseOptions = mongooseType.options || {};
|
|
1609
|
-
var formData = mongooseOptions.form || {};
|
|
1610
|
-
if (mongooseType.schema && !formData.hidden) {
|
|
1611
|
-
if (doRecursion && destForm) {
|
|
1612
|
-
var schemaSchema = [];
|
|
1613
|
-
handleSchema('Nested ' + field, mongooseType.schema, schemaSchema, null, field + '.', true, $scope, ctrlState);
|
|
1614
|
-
var sectionInstructions = basicInstructions(field, formData, prefix);
|
|
1615
|
-
sectionInstructions.schema = schemaSchema;
|
|
1616
|
-
if (formData.tab) {
|
|
1617
|
-
handletabInfo(formData.tab, sectionInstructions);
|
|
1618
|
-
}
|
|
1619
|
-
if (formData.order !== undefined) {
|
|
1620
|
-
destForm.splice(formData.order, 0, sectionInstructions);
|
|
1621
|
-
}
|
|
1622
|
-
else {
|
|
1623
|
-
destForm.push(sectionInstructions);
|
|
1624
|
-
}
|
|
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);
|
|
1625
1937
|
}
|
|
1626
1938
|
}
|
|
1627
|
-
else {
|
|
1628
|
-
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
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);
|
|
1634
1950
|
}
|
|
1635
1951
|
if (formData.order !== undefined) {
|
|
1636
|
-
destForm.splice(formData.order, 0,
|
|
1952
|
+
destForm.splice(formData.order, 0, sectionInstructions);
|
|
1637
1953
|
}
|
|
1638
1954
|
else {
|
|
1639
|
-
destForm.push(
|
|
1955
|
+
destForm.push(sectionInstructions);
|
|
1640
1956
|
}
|
|
1641
1957
|
}
|
|
1642
1958
|
}
|
|
1643
|
-
|
|
1644
|
-
|
|
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
|
+
}
|
|
1645
1978
|
}
|
|
1646
1979
|
}
|
|
1647
1980
|
}
|
|
@@ -1665,6 +1998,9 @@ var fng;
|
|
|
1665
1998
|
// console.log($scope.tabs[0]['title'])
|
|
1666
1999
|
// $location.hash($scope.tabs[0]['title']);
|
|
1667
2000
|
// }
|
|
2001
|
+
if (typeof $scope.onSchemaProcessed === "function") {
|
|
2002
|
+
$scope.onSchemaProcessed(description, description.slice(0, 5) === 'Main ' ? $scope.baseSchema() : destForm);
|
|
2003
|
+
}
|
|
1668
2004
|
if (destList && destList.length === 0) {
|
|
1669
2005
|
handleEmptyList(description, destList, destForm, source);
|
|
1670
2006
|
}
|
|
@@ -1674,7 +2010,23 @@ var fng;
|
|
|
1674
2010
|
formInstructions.options = recordHandler.suffixCleanId(formInstructions, 'Options');
|
|
1675
2011
|
formInstructions.ids = recordHandler.suffixCleanId(formInstructions, '_ids');
|
|
1676
2012
|
if (!formInstructions.hidden) {
|
|
1677
|
-
|
|
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));
|
|
2029
|
+
}
|
|
1678
2030
|
}
|
|
1679
2031
|
}
|
|
1680
2032
|
if (mongooseType.caster) {
|
|
@@ -1685,7 +2037,7 @@ var fng;
|
|
|
1685
2037
|
angular.extend(formInstructions, mongooseType.options.form);
|
|
1686
2038
|
}
|
|
1687
2039
|
}
|
|
1688
|
-
if (mongooseType.instance === 'String') {
|
|
2040
|
+
if (mongooseType.instance === 'String' || (mongooseType.instance === 'ObjectID' && formInstructions.asText)) {
|
|
1689
2041
|
if (mongooseOptions.enum) {
|
|
1690
2042
|
formInstructions.type = formInstructions.type || 'select';
|
|
1691
2043
|
if (formInstructions.select2) {
|
|
@@ -1707,34 +2059,45 @@ var fng;
|
|
|
1707
2059
|
}
|
|
1708
2060
|
else if (mongooseType.instance === 'ObjectID') {
|
|
1709
2061
|
formInstructions.ref = mongooseOptions.ref;
|
|
1710
|
-
if (formInstructions.link
|
|
1711
|
-
formInstructions.
|
|
1712
|
-
|
|
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
|
+
}
|
|
1713
2073
|
formInstructions.form = formInstructions.link.form;
|
|
2074
|
+
formInstructions.linktab = formInstructions.link.linktab;
|
|
1714
2075
|
delete formInstructions.link;
|
|
1715
2076
|
}
|
|
1716
|
-
|
|
2077
|
+
if (formInstructions.type !== 'link') {
|
|
1717
2078
|
formInstructions.type = 'select';
|
|
1718
2079
|
if (formInstructions.select2 || (mongooseOptions.form && mongooseOptions.form.select2)) {
|
|
1719
2080
|
console.log('support for fng-select2 has been removed in 0.8.3 - please convert to fng-ui-select');
|
|
1720
2081
|
}
|
|
1721
|
-
else if (!formInstructions.directive || (!formInstructions.noLookup && (!formInstructions[$filter('camelCase')(formInstructions.directive)] || !formInstructions[$filter('camelCase')(formInstructions.directive)].fngAjax))) {
|
|
2082
|
+
else if ((!formInstructions.directive || (!formInstructions.noLookup && (!formInstructions[$filter('camelCase')(formInstructions.directive)] || !formInstructions[$filter('camelCase')(formInstructions.directive)].fngAjax)))) {
|
|
1722
2083
|
performLookupSelect();
|
|
1723
2084
|
}
|
|
1724
2085
|
}
|
|
1725
2086
|
}
|
|
1726
2087
|
else if (mongooseType.instance === 'Date') {
|
|
1727
2088
|
if (!formInstructions.type) {
|
|
2089
|
+
formInstructions.intType = 'date';
|
|
1728
2090
|
if (formInstructions.readonly) {
|
|
1729
2091
|
formInstructions.type = 'text';
|
|
1730
2092
|
}
|
|
1731
2093
|
else if (formInstructions.directive) {
|
|
1732
|
-
formInstructions.type = 'text';
|
|
2094
|
+
formInstructions.type = 'text';
|
|
1733
2095
|
}
|
|
1734
2096
|
else {
|
|
1735
2097
|
try {
|
|
1736
2098
|
formInstructions.add = formInstructions.add || '';
|
|
1737
|
-
|
|
2099
|
+
// Check whether DatePicker is installed
|
|
2100
|
+
angular.module('ui.date').requires;
|
|
1738
2101
|
formInstructions.type = 'text';
|
|
1739
2102
|
formInstructions.add += ' ui-date ui-date-format ';
|
|
1740
2103
|
// formInstructions.add += ' ui-date ui-date-format datepicker-popup-fix ';
|
|
@@ -1747,10 +2110,10 @@ var fng;
|
|
|
1747
2110
|
}
|
|
1748
2111
|
}
|
|
1749
2112
|
else if (mongooseType.instance.toLowerCase() === 'boolean') {
|
|
1750
|
-
formInstructions.type = 'checkbox';
|
|
2113
|
+
formInstructions.type = formInstructions.type || 'checkbox';
|
|
1751
2114
|
}
|
|
1752
2115
|
else if (mongooseType.instance === 'Number') {
|
|
1753
|
-
formInstructions.type = 'number';
|
|
2116
|
+
formInstructions.type = formInstructions.type || 'number';
|
|
1754
2117
|
if (mongooseOptions.min !== undefined) {
|
|
1755
2118
|
formInstructions.add = 'min="' + mongooseOptions.min + '" ' + (formInstructions.add || '');
|
|
1756
2119
|
}
|
|
@@ -1770,11 +2133,17 @@ var fng;
|
|
|
1770
2133
|
if (mongooseOptions.readonly) {
|
|
1771
2134
|
formInstructions['readonly'] = true;
|
|
1772
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
|
+
}
|
|
1773
2142
|
return formInstructions;
|
|
1774
2143
|
}
|
|
1775
|
-
function getArrayFieldToExtend(fieldName, $scope) {
|
|
2144
|
+
function getArrayFieldToExtend(fieldName, $scope, modelOverride) {
|
|
1776
2145
|
var fieldParts = fieldName.split('.');
|
|
1777
|
-
var arrayField = $scope.record;
|
|
2146
|
+
var arrayField = modelOverride || $scope.record;
|
|
1778
2147
|
for (var i = 0, l = fieldParts.length; i < l; i++) {
|
|
1779
2148
|
if (!arrayField[fieldParts[i]]) {
|
|
1780
2149
|
if (i === l - 1) {
|
|
@@ -1891,6 +2260,9 @@ var fng;
|
|
|
1891
2260
|
generateEditUrl: function generateEditUrl(obj, $scope) {
|
|
1892
2261
|
return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/edit');
|
|
1893
2262
|
},
|
|
2263
|
+
generateViewUrl: function generateViewUrl(obj, $scope) {
|
|
2264
|
+
return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/view');
|
|
2265
|
+
},
|
|
1894
2266
|
generateNewUrl: function generateNewUrl($scope) {
|
|
1895
2267
|
return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + 'new');
|
|
1896
2268
|
},
|
|
@@ -1975,25 +2347,55 @@ var fng;
|
|
|
1975
2347
|
}
|
|
1976
2348
|
return forceNextTime;
|
|
1977
2349
|
},
|
|
1978
|
-
add: function add(fieldName, $event, $scope) {
|
|
1979
|
-
var
|
|
1980
|
-
|
|
1981
|
-
|
|
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
|
+
}
|
|
1982
2369
|
},
|
|
1983
|
-
unshift: function unshift(fieldName, $event, $scope) {
|
|
1984
|
-
var arrayField = getArrayFieldToExtend(fieldName, $scope);
|
|
2370
|
+
unshift: function unshift(fieldName, $event, $scope, modelOverride) {
|
|
2371
|
+
var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
|
|
1985
2372
|
arrayField.unshift({});
|
|
1986
2373
|
$scope.setFormDirty($event);
|
|
1987
2374
|
},
|
|
1988
|
-
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
|
+
}
|
|
1989
2382
|
// Remove an element from an array
|
|
1990
|
-
var
|
|
1991
|
-
var
|
|
1992
|
-
|
|
1993
|
-
|
|
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);
|
|
1994
2398
|
}
|
|
1995
|
-
arrayField.splice(value, 1);
|
|
1996
|
-
$scope.setFormDirty($event);
|
|
1997
2399
|
},
|
|
1998
2400
|
hasError: function hasError(formName, name, index, $scope) {
|
|
1999
2401
|
var result = false;
|
|
@@ -2005,7 +2407,7 @@ var fng;
|
|
|
2005
2407
|
// Cannot assume that directives will use the same methods
|
|
2006
2408
|
if (form) {
|
|
2007
2409
|
var field_1 = form[name];
|
|
2008
|
-
if (field_1 && field_1.$invalid) {
|
|
2410
|
+
if (field_1 && field_1.$invalid && !field_1.$$attr.readonly) {
|
|
2009
2411
|
if (field_1.$dirty) {
|
|
2010
2412
|
result = true;
|
|
2011
2413
|
}
|
|
@@ -2025,7 +2427,7 @@ var fng;
|
|
|
2025
2427
|
}
|
|
2026
2428
|
return result;
|
|
2027
2429
|
},
|
|
2028
|
-
decorateScope: function decorateScope($scope, formGeneratorInstance, recordHandlerInstance, sharedData) {
|
|
2430
|
+
decorateScope: function decorateScope($scope, formGeneratorInstance, recordHandlerInstance, sharedData, pseudoUrl) {
|
|
2029
2431
|
$scope.record = sharedData.record;
|
|
2030
2432
|
$scope.phase = 'init';
|
|
2031
2433
|
$scope.disableFunctions = sharedData.disableFunctions;
|
|
@@ -2036,21 +2438,40 @@ var fng;
|
|
|
2036
2438
|
$scope.listSchema = [];
|
|
2037
2439
|
$scope.recordList = [];
|
|
2038
2440
|
$scope.dataDependencies = {};
|
|
2441
|
+
$scope.internalLookups = [];
|
|
2442
|
+
$scope.listLookups = [];
|
|
2039
2443
|
$scope.conversions = {};
|
|
2040
2444
|
$scope.pageSize = 60;
|
|
2041
2445
|
$scope.pagesLoaded = 0;
|
|
2042
2446
|
sharedData.baseScope = $scope;
|
|
2447
|
+
securityService.decorateSecurableScope($scope, { pseudoUrl: pseudoUrl });
|
|
2043
2448
|
$scope.generateEditUrl = function (obj) {
|
|
2044
2449
|
return formGeneratorInstance.generateEditUrl(obj, $scope);
|
|
2045
2450
|
};
|
|
2451
|
+
$scope.generateViewUrl = function (obj) {
|
|
2452
|
+
return formGeneratorInstance.generateViewUrl(obj, $scope);
|
|
2453
|
+
};
|
|
2046
2454
|
$scope.generateNewUrl = function () {
|
|
2047
2455
|
return formGeneratorInstance.generateNewUrl($scope);
|
|
2048
2456
|
};
|
|
2049
2457
|
$scope.scrollTheList = function () {
|
|
2050
|
-
|
|
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
|
+
}
|
|
2051
2472
|
};
|
|
2052
2473
|
$scope.getListData = function (record, fieldName) {
|
|
2053
|
-
return recordHandlerInstance.getListData(
|
|
2474
|
+
return recordHandlerInstance.getListData(record, fieldName, $scope.listSchema, $scope);
|
|
2054
2475
|
};
|
|
2055
2476
|
$scope.setPristine = function (clearErrors) {
|
|
2056
2477
|
if (clearErrors) {
|
|
@@ -2072,17 +2493,17 @@ var fng;
|
|
|
2072
2493
|
console.log('setFormDirty called without an event (fine in a unit test)');
|
|
2073
2494
|
}
|
|
2074
2495
|
};
|
|
2075
|
-
$scope.add = function (fieldName, $event) {
|
|
2076
|
-
return formGeneratorInstance.add(fieldName, $event, $scope);
|
|
2496
|
+
$scope.add = function (fieldName, $event, modelOverride) {
|
|
2497
|
+
return formGeneratorInstance.add(fieldName, $event, $scope, modelOverride);
|
|
2077
2498
|
};
|
|
2078
2499
|
$scope.hasError = function (form, name, index) {
|
|
2079
2500
|
return formGeneratorInstance.hasError(form, name, index, $scope);
|
|
2080
2501
|
};
|
|
2081
|
-
$scope.unshift = function (fieldName, $event) {
|
|
2082
|
-
return formGeneratorInstance.unshift(fieldName, $event, $scope);
|
|
2502
|
+
$scope.unshift = function (fieldName, $event, modelOverride) {
|
|
2503
|
+
return formGeneratorInstance.unshift(fieldName, $event, $scope, modelOverride);
|
|
2083
2504
|
};
|
|
2084
|
-
$scope.remove = function (fieldName, value, $event) {
|
|
2085
|
-
return formGeneratorInstance.remove(fieldName, value, $event, $scope);
|
|
2505
|
+
$scope.remove = function (fieldName, value, $event, modelOverride) {
|
|
2506
|
+
return formGeneratorInstance.remove(fieldName, value, $event, $scope, modelOverride);
|
|
2086
2507
|
};
|
|
2087
2508
|
$scope.baseSchema = function () {
|
|
2088
2509
|
return ($scope.tabs.length ? $scope.tabs : $scope.formSchema);
|
|
@@ -2097,18 +2518,17 @@ var fng;
|
|
|
2097
2518
|
};
|
|
2098
2519
|
}
|
|
2099
2520
|
services.formGenerator = formGenerator;
|
|
2100
|
-
formGenerator.$inject = ["$
|
|
2521
|
+
formGenerator.$inject = ["$filter", "routingService", "recordHandler", "securityService"];
|
|
2101
2522
|
})(services = fng.services || (fng.services = {}));
|
|
2102
2523
|
})(fng || (fng = {}));
|
|
2103
|
-
/// <reference path="
|
|
2104
|
-
/// <reference path="../fng-types" />
|
|
2524
|
+
/// <reference path="../../index.d.ts" />
|
|
2105
2525
|
var fng;
|
|
2106
2526
|
(function (fng) {
|
|
2107
2527
|
var services;
|
|
2108
2528
|
(function (services) {
|
|
2109
2529
|
/*@ngInject*/
|
|
2110
|
-
formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService"];
|
|
2111
|
-
function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService) {
|
|
2530
|
+
formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "securityService", "$filter"];
|
|
2531
|
+
function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, securityService, $filter) {
|
|
2112
2532
|
function generateNgShow(showWhen, model) {
|
|
2113
2533
|
function evaluateSide(side) {
|
|
2114
2534
|
var result = side;
|
|
@@ -2136,29 +2556,190 @@ var fng;
|
|
|
2136
2556
|
}
|
|
2137
2557
|
return evaluateSide(showWhen.lhs) + conditionSymbols[conditionPos] + evaluateSide(showWhen.rhs);
|
|
2138
2558
|
}
|
|
2139
|
-
var isHorizontalStyle = function isHorizontalStyle(formStyle) {
|
|
2140
|
-
|
|
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));
|
|
2141
2565
|
};
|
|
2142
2566
|
function glyphClass() {
|
|
2143
|
-
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;
|
|
2144
2688
|
}
|
|
2145
2689
|
return {
|
|
2146
2690
|
isHorizontalStyle: isHorizontalStyle,
|
|
2691
|
+
isArrayElement: isArrayElement,
|
|
2147
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
|
+
}
|
|
2148
2731
|
var classes = info.classes || '';
|
|
2149
2732
|
var template = '';
|
|
2150
2733
|
var closeTag = '';
|
|
2151
|
-
var insert = '';
|
|
2152
2734
|
info.showWhen = info.showWhen || info.showwhen; // deal with use within a directive
|
|
2153
2735
|
if (info.showWhen) {
|
|
2154
2736
|
if (typeof info.showWhen === 'string') {
|
|
2155
|
-
insert += 'ng-show="' + info.showWhen + '"';
|
|
2737
|
+
insert += ' ng-show="' + info.showWhen + '"';
|
|
2156
2738
|
}
|
|
2157
2739
|
else {
|
|
2158
|
-
insert += 'ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
|
|
2740
|
+
insert += ' ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
|
|
2159
2741
|
}
|
|
2160
2742
|
}
|
|
2161
|
-
insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
|
|
2162
2743
|
if (cssFrameworkService.framework() === 'bs3') {
|
|
2163
2744
|
classes += ' form-group';
|
|
2164
2745
|
if (options.formstyle === 'vertical' && info.size !== 'block-level') {
|
|
@@ -2183,7 +2764,7 @@ var fng;
|
|
|
2183
2764
|
closeTag += '</div>';
|
|
2184
2765
|
}
|
|
2185
2766
|
else {
|
|
2186
|
-
if (isHorizontalStyle(options.formstyle)) {
|
|
2767
|
+
if (isHorizontalStyle(options.formstyle, true)) {
|
|
2187
2768
|
template += '<div' + addAllService.addAll(scope, 'Group', 'control-group', options);
|
|
2188
2769
|
closeTag = '</div>';
|
|
2189
2770
|
}
|
|
@@ -2197,11 +2778,13 @@ var fng;
|
|
|
2197
2778
|
},
|
|
2198
2779
|
label: function label(scope, fieldInfo, addButtonMarkup, options) {
|
|
2199
2780
|
var labelHTML = '';
|
|
2200
|
-
if ((cssFrameworkService.framework() === 'bs3' || (options.formstyle
|
|
2781
|
+
if ((cssFrameworkService.framework() === 'bs3' || (!['inline', 'stacked'].includes(options.formstyle) && fieldInfo.label !== '')) || addButtonMarkup) {
|
|
2201
2782
|
labelHTML = '<label';
|
|
2202
2783
|
var classes = 'control-label';
|
|
2203
|
-
if (isHorizontalStyle(options.formstyle)) {
|
|
2204
|
-
|
|
2784
|
+
if (isHorizontalStyle(options.formstyle, false)) {
|
|
2785
|
+
if (!fieldInfo.linklabel) {
|
|
2786
|
+
labelHTML += ' for="' + fieldInfo.id + '"';
|
|
2787
|
+
}
|
|
2205
2788
|
if (typeof fieldInfo.labelDefaultClass !== 'undefined') {
|
|
2206
2789
|
// Override default label class (can be empty)
|
|
2207
2790
|
classes += ' ' + fieldInfo.labelDefaultClass;
|
|
@@ -2210,15 +2793,27 @@ var fng;
|
|
|
2210
2793
|
classes += ' col-sm-3';
|
|
2211
2794
|
}
|
|
2212
2795
|
}
|
|
2213
|
-
else if (options.formstyle
|
|
2796
|
+
else if (['inline', 'stacked'].includes(options.formstyle)) {
|
|
2214
2797
|
labelHTML += ' for="' + fieldInfo.id + '"';
|
|
2215
2798
|
classes += ' sr-only';
|
|
2216
2799
|
}
|
|
2217
2800
|
labelHTML += addAllService.addAll(scope, 'Label', null, options) + ' class="' + classes + '">' + fieldInfo.label;
|
|
2218
2801
|
if (addButtonMarkup) {
|
|
2219
|
-
|
|
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>");
|
|
2220
2804
|
}
|
|
2221
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
|
+
}
|
|
2222
2817
|
}
|
|
2223
2818
|
return labelHTML;
|
|
2224
2819
|
},
|
|
@@ -2238,13 +2833,27 @@ var fng;
|
|
|
2238
2833
|
else {
|
|
2239
2834
|
sizeClassBS2 = (fieldInfo.size ? ' input-' + fieldInfo.size : '');
|
|
2240
2835
|
}
|
|
2241
|
-
if (options.formstyle
|
|
2836
|
+
if (['inline', 'stacked'].includes(options.formstyle)) {
|
|
2242
2837
|
placeHolder = placeHolder || fieldInfo.label;
|
|
2243
2838
|
}
|
|
2244
|
-
common = 'ng-model="' + modelString + '"'
|
|
2245
|
-
|
|
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
|
+
}
|
|
2246
2852
|
if (fieldInfo.popup) {
|
|
2247
|
-
common +=
|
|
2853
|
+
common += " title=\"".concat(fieldInfo.popup, "\"");
|
|
2854
|
+
}
|
|
2855
|
+
if (fieldInfo.ariaLabel) {
|
|
2856
|
+
common += " aria-label=\"".concat(fieldInfo.ariaLabel, "\"");
|
|
2248
2857
|
}
|
|
2249
2858
|
common += addAllService.addAll(scope, 'Field', null, options);
|
|
2250
2859
|
return {
|
|
@@ -2256,28 +2865,39 @@ var fng;
|
|
|
2256
2865
|
};
|
|
2257
2866
|
},
|
|
2258
2867
|
inputChrome: function inputChrome(value, fieldInfo, options, markupVars) {
|
|
2259
|
-
if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle) && fieldInfo.type !== 'checkbox') {
|
|
2868
|
+
if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle, true) && fieldInfo.type !== 'checkbox') {
|
|
2260
2869
|
value = '<div class="bs3-input ' + markupVars.sizeClassBS3 + '">' + value + '</div>';
|
|
2261
2870
|
}
|
|
2262
2871
|
// Hack to cope with inline help in directives
|
|
2263
2872
|
var inlineHelp = (fieldInfo.helpInline || '') + (fieldInfo.helpinline || '');
|
|
2264
2873
|
if (inlineHelp.length > 0) {
|
|
2265
|
-
|
|
2266
|
-
|
|
2267
|
-
|
|
2268
|
-
|
|
2269
|
-
|
|
2270
|
-
|
|
2271
|
-
|
|
2272
|
-
|
|
2273
|
-
|
|
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
|
+
}
|
|
2274
2888
|
if (fieldInfo.help) {
|
|
2275
|
-
value += '<
|
|
2889
|
+
value += '<div class="help-block">' + fieldInfo.help + '</div>';
|
|
2276
2890
|
}
|
|
2277
2891
|
return value;
|
|
2278
2892
|
},
|
|
2279
2893
|
generateSimpleInput: function generateSimpleInput(common, fieldInfo, options) {
|
|
2280
|
-
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
|
+
}
|
|
2281
2901
|
if (options.formstyle === 'inline' && cssFrameworkService.framework() === 'bs2' && !fieldInfo.size) {
|
|
2282
2902
|
result += 'class="input-small"';
|
|
2283
2903
|
}
|
|
@@ -2286,7 +2906,7 @@ var fng;
|
|
|
2286
2906
|
},
|
|
2287
2907
|
controlDivClasses: function controlDivClasses(options) {
|
|
2288
2908
|
var result = [];
|
|
2289
|
-
if (isHorizontalStyle(options.formstyle)) {
|
|
2909
|
+
if (isHorizontalStyle(options.formstyle, false)) {
|
|
2290
2910
|
result.push(cssFrameworkService.framework() === 'bs2' ? 'controls' : 'col-sm-9');
|
|
2291
2911
|
}
|
|
2292
2912
|
return result;
|
|
@@ -2297,18 +2917,21 @@ var fng;
|
|
|
2297
2917
|
}
|
|
2298
2918
|
return inputMarkup;
|
|
2299
2919
|
},
|
|
2300
|
-
handleArrayInputAndControlDiv: function handleArrayInputAndControlDiv(inputMarkup, controlDivClasses, info, options) {
|
|
2301
|
-
var
|
|
2302
|
-
|
|
2303
|
-
|
|
2304
|
-
|
|
2305
|
-
|
|
2306
|
-
|
|
2307
|
-
|
|
2308
|
-
|
|
2309
|
-
|
|
2310
|
-
}
|
|
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);
|
|
2311
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
|
+
}
|
|
2312
2935
|
return result;
|
|
2313
2936
|
},
|
|
2314
2937
|
addTextInputMarkup: function addTextInputMarkup(allInputsVars, fieldInfo, requiredStr) {
|
|
@@ -2320,9 +2943,12 @@ var fng;
|
|
|
2320
2943
|
if (fieldInfo.add) {
|
|
2321
2944
|
result += ' ' + fieldInfo.add + ' ';
|
|
2322
2945
|
}
|
|
2323
|
-
result += requiredStr
|
|
2946
|
+
result += requiredStr;
|
|
2324
2947
|
return result;
|
|
2325
|
-
}
|
|
2948
|
+
},
|
|
2949
|
+
handleReadOnlyDisabled: handleReadOnlyDisabled,
|
|
2950
|
+
generateArrayElementIdString: generateArrayElementIdString,
|
|
2951
|
+
genDisableableAncestorStr: genDisableableAncestorStr
|
|
2326
2952
|
};
|
|
2327
2953
|
}
|
|
2328
2954
|
services.formMarkupHelper = formMarkupHelper;
|
|
@@ -2361,110 +2987,267 @@ var fng;
|
|
|
2361
2987
|
/*@ngInject*/
|
|
2362
2988
|
pluginHelper.$inject = ["formMarkupHelper"];
|
|
2363
2989
|
function pluginHelper(formMarkupHelper) {
|
|
2364
|
-
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
|
|
2368
|
-
|
|
2369
|
-
|
|
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, "\"");
|
|
2370
3099
|
}
|
|
2371
|
-
|
|
2372
|
-
|
|
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;
|
|
2373
3122
|
}
|
|
2374
|
-
else
|
|
2375
|
-
|
|
3123
|
+
else {
|
|
3124
|
+
return retVal;
|
|
2376
3125
|
}
|
|
2377
|
-
return retVal;
|
|
2378
3126
|
}
|
|
2379
|
-
|
|
2380
|
-
|
|
2381
|
-
|
|
2382
|
-
|
|
2383
|
-
|
|
2384
|
-
|
|
2385
|
-
|
|
2386
|
-
|
|
2387
|
-
|
|
2388
|
-
|
|
2389
|
-
|
|
2390
|
-
|
|
2391
|
-
|
|
2392
|
-
|
|
2393
|
-
|
|
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]);
|
|
2394
3144
|
}
|
|
2395
3145
|
}
|
|
2396
|
-
|
|
2397
|
-
}
|
|
2398
|
-
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
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);
|
|
2407
3168
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2410
|
-
|
|
2411
|
-
|
|
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 "";
|
|
3176
|
+
}
|
|
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);
|
|
2412
3182
|
}
|
|
2413
|
-
|
|
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) {
|
|
2414
3188
|
// Schema handling - need to massage the ngModel and the id
|
|
2415
|
-
var modelBase = model +
|
|
2416
|
-
var compoundName = info.name;
|
|
3189
|
+
var modelBase = attrs.model + ".";
|
|
2417
3190
|
var root = options.subschemaroot;
|
|
2418
|
-
var lastPart =
|
|
2419
|
-
modelString = modelBase;
|
|
2420
|
-
if (options.
|
|
2421
|
-
modelString
|
|
2422
|
-
|
|
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;
|
|
2423
3196
|
}
|
|
2424
3197
|
else {
|
|
2425
|
-
modelString +=
|
|
2426
|
-
|
|
2427
|
-
idString = modelString.slice(modelBase.length).replace(/\./g, '-') + '-subkey' + options.subkeyno + '-' + lastPart;
|
|
2428
|
-
modelString += '[' + '$_arrayOffset_' + root.replace(/\./g, '_') + '_' + options.subkeyno + '].' + lastPart;
|
|
2429
|
-
}
|
|
2430
|
-
else {
|
|
2431
|
-
modelString += '[$index].' + lastPart;
|
|
2432
|
-
idString = null;
|
|
2433
|
-
nameString = compoundName.replace(/\./g, '-');
|
|
2434
|
-
}
|
|
3198
|
+
modelString += "[$index]." + lastPart;
|
|
3199
|
+
nameString = info.name.replace(/\./g, "-");
|
|
2435
3200
|
}
|
|
2436
3201
|
}
|
|
2437
3202
|
var buildingBlocks = formMarkupHelper.allInputsVars(scope, info, options, modelString, idString, nameString);
|
|
2438
3203
|
buildingBlocks.modelString = modelString;
|
|
2439
|
-
|
|
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
|
+
}
|
|
2440
3216
|
elementHtml += fieldChrome.closeTag;
|
|
2441
3217
|
return elementHtml;
|
|
2442
3218
|
},
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2453
|
-
|
|
2454
|
-
|
|
2455
|
-
|
|
2456
|
-
|
|
2457
|
-
|
|
2458
|
-
|
|
2459
|
-
|
|
2460
|
-
|
|
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);
|
|
2461
3244
|
}
|
|
2462
3245
|
};
|
|
2463
3246
|
}
|
|
2464
3247
|
services.pluginHelper = pluginHelper;
|
|
2465
3248
|
})(services = fng.services || (fng.services = {}));
|
|
2466
3249
|
})(fng || (fng = {}));
|
|
2467
|
-
/// <reference path="
|
|
3250
|
+
/// <reference path="../../index.d.ts" />
|
|
2468
3251
|
var fng;
|
|
2469
3252
|
(function (fng) {
|
|
2470
3253
|
var services;
|
|
@@ -2476,21 +3259,32 @@ var fng;
|
|
|
2476
3259
|
*
|
|
2477
3260
|
*/
|
|
2478
3261
|
/*@ngInject*/
|
|
2479
|
-
recordHandler.$inject = ["$
|
|
2480
|
-
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) {
|
|
3264
|
+
// TODO: Put this in a service
|
|
3265
|
+
var makeMongoId = function (rnd) {
|
|
3266
|
+
if (rnd === void 0) { rnd = function (r16) { return Math.floor(r16).toString(16); }; }
|
|
3267
|
+
return rnd(Date.now() / 1000) + " ".repeat(16).replace(/./g, function () { return rnd(Math.random() * 16); });
|
|
3268
|
+
};
|
|
3269
|
+
function _handleCancel(resp) {
|
|
3270
|
+
if (["cancel", "backdrop click", "escape key press"].indexOf(resp) === -1) {
|
|
3271
|
+
throw resp;
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
2481
3274
|
var suffixCleanId = function suffixCleanId(inst, suffix) {
|
|
2482
|
-
return (inst.id ||
|
|
3275
|
+
return (inst.id || "f_" + inst.name).replace(/\./g, "_") + suffix;
|
|
2483
3276
|
};
|
|
2484
|
-
var walkTree = function (object, fieldname, element) {
|
|
3277
|
+
var walkTree = function (object, fieldname, element, insertIntermediateObjects) {
|
|
2485
3278
|
// Walk through subdocs to find the required key
|
|
2486
3279
|
// for instance walkTree(master,'address.street.number',element)
|
|
2487
3280
|
// called by getData and setData
|
|
3281
|
+
if (insertIntermediateObjects === void 0) { insertIntermediateObjects = false; }
|
|
2488
3282
|
// element is used when accessing in the context of a input, as the id (like exams-2-grader)
|
|
2489
3283
|
// gives us the element of an array (one level down only for now). Leaving element blank returns the whole array
|
|
2490
|
-
var parts = fieldname.split(
|
|
3284
|
+
var parts = fieldname.split("."), higherLevels = parts.length - 1, workingRec = object;
|
|
2491
3285
|
for (var i = 0; i < higherLevels; i++) {
|
|
2492
3286
|
if (!workingRec) {
|
|
2493
|
-
throw new Error("walkTree failed: Object = "
|
|
3287
|
+
throw new Error("walkTree failed: Object = ".concat(object, ", fieldname = ").concat(fieldname, ", i = ").concat(i));
|
|
2494
3288
|
}
|
|
2495
3289
|
if (angular.isArray(workingRec)) {
|
|
2496
3290
|
workingRec = _.map(workingRec, function (obj) {
|
|
@@ -2498,18 +3292,21 @@ var fng;
|
|
|
2498
3292
|
});
|
|
2499
3293
|
}
|
|
2500
3294
|
else {
|
|
3295
|
+
if (insertIntermediateObjects && !workingRec[parts[i]]) {
|
|
3296
|
+
workingRec[parts[i]] = {};
|
|
3297
|
+
}
|
|
2501
3298
|
workingRec = workingRec[parts[i]];
|
|
2502
3299
|
}
|
|
2503
|
-
if (angular.isArray(workingRec) && typeof element !==
|
|
2504
|
-
if (element.scope && typeof element.scope ===
|
|
3300
|
+
if (angular.isArray(workingRec) && typeof element !== "undefined") {
|
|
3301
|
+
if (element.scope && typeof element.scope === "function") {
|
|
2505
3302
|
// If we come across an array we need to find the correct position, if we have an element
|
|
2506
3303
|
workingRec = workingRec[element.scope().$index];
|
|
2507
3304
|
}
|
|
2508
|
-
else if (typeof element ===
|
|
3305
|
+
else if (typeof element === "number") {
|
|
2509
3306
|
workingRec = workingRec[element];
|
|
2510
3307
|
}
|
|
2511
3308
|
else {
|
|
2512
|
-
throw new Error(
|
|
3309
|
+
throw new Error("Unsupported element type in walkTree " + fieldname);
|
|
2513
3310
|
}
|
|
2514
3311
|
}
|
|
2515
3312
|
if (!workingRec) {
|
|
@@ -2522,15 +3319,20 @@ var fng;
|
|
|
2522
3319
|
};
|
|
2523
3320
|
};
|
|
2524
3321
|
var setData = function setData(object, fieldname, element, value) {
|
|
2525
|
-
var leafData = walkTree(object, fieldname, element);
|
|
3322
|
+
var leafData = walkTree(object, fieldname, element, !!value);
|
|
2526
3323
|
if (leafData.lastObject && leafData.key) {
|
|
2527
|
-
if (
|
|
2528
|
-
|
|
2529
|
-
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;
|
|
2530
3332
|
}
|
|
2531
3333
|
}
|
|
2532
3334
|
else {
|
|
2533
|
-
leafData.lastObject[leafData.key]
|
|
3335
|
+
delete leafData.lastObject[leafData.key];
|
|
2534
3336
|
}
|
|
2535
3337
|
}
|
|
2536
3338
|
};
|
|
@@ -2549,11 +3351,17 @@ var fng;
|
|
|
2549
3351
|
}
|
|
2550
3352
|
return retVal;
|
|
2551
3353
|
};
|
|
2552
|
-
var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState) {
|
|
3354
|
+
var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState, ignoreDirty) {
|
|
3355
|
+
if (ignoreDirty === void 0) { ignoreDirty = false; }
|
|
2553
3356
|
// Update the master and the record with the lookup values, master first
|
|
2554
|
-
if (!$scope.topLevelFormName || $scope[$scope.topLevelFormName].$pristine) {
|
|
3357
|
+
if (!$scope.topLevelFormName || ($scope[$scope.topLevelFormName] && (ignoreDirty || $scope[$scope.topLevelFormName].$pristine))) {
|
|
2555
3358
|
updateObject(schemaElement.name, ctrlState.master, function (value) {
|
|
2556
|
-
|
|
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
|
+
}
|
|
2557
3365
|
});
|
|
2558
3366
|
// Then copy the converted keys from master into record
|
|
2559
3367
|
var newVal = getData(ctrlState.master, schemaElement.name);
|
|
@@ -2564,38 +3372,29 @@ var fng;
|
|
|
2564
3372
|
};
|
|
2565
3373
|
// Split a field name into the next level and all following levels
|
|
2566
3374
|
function splitFieldName(aFieldName) {
|
|
2567
|
-
var nesting = aFieldName.split(
|
|
3375
|
+
var nesting = aFieldName.split("."), result = [nesting[0]];
|
|
2568
3376
|
if (nesting.length > 1) {
|
|
2569
|
-
result.push(nesting.slice(1).join(
|
|
3377
|
+
result.push(nesting.slice(1).join("."));
|
|
2570
3378
|
}
|
|
2571
3379
|
return result;
|
|
2572
3380
|
}
|
|
2573
|
-
var getListData = function getListData(
|
|
3381
|
+
var getListData = function getListData(record, fieldName, listSchema, $scope) {
|
|
2574
3382
|
if (listSchema === void 0) { listSchema = null; }
|
|
2575
|
-
var retVal = record;
|
|
2576
|
-
var nests = fieldName.split('.');
|
|
2577
|
-
for (var i = 0; i < nests.length; i++) {
|
|
2578
|
-
if (retVal !== undefined && retVal !== null && nests && nests[i]) {
|
|
2579
|
-
retVal = retVal[nests[i]];
|
|
2580
|
-
}
|
|
2581
|
-
}
|
|
2582
|
-
if (retVal === undefined) {
|
|
2583
|
-
retVal = '';
|
|
2584
|
-
}
|
|
3383
|
+
var retVal = getData(record, fieldName) || "";
|
|
2585
3384
|
if (retVal && listSchema) {
|
|
2586
3385
|
// Convert list fields as per instructions in params (ideally should be the same as what is found in data_form getListFields
|
|
2587
|
-
var schemaElm = _.find(listSchema, function (elm) { return (elm[
|
|
3386
|
+
var schemaElm = _.find(listSchema, function (elm) { return (elm["name"] === fieldName); });
|
|
2588
3387
|
if (schemaElm) {
|
|
2589
|
-
switch (schemaElm[
|
|
3388
|
+
switch (schemaElm["params"]) {
|
|
2590
3389
|
case undefined:
|
|
2591
3390
|
break;
|
|
2592
|
-
case
|
|
3391
|
+
case "timestamp":
|
|
2593
3392
|
var timestamp = retVal.toString().substring(0, 8);
|
|
2594
3393
|
var date = new Date(parseInt(timestamp, 16) * 1000);
|
|
2595
|
-
retVal = date.toLocaleDateString() +
|
|
3394
|
+
retVal = date.toLocaleDateString() + " " + date.toLocaleTimeString();
|
|
2596
3395
|
break;
|
|
2597
3396
|
default:
|
|
2598
|
-
retVal = $scope.dataEventFunctions[schemaElm[
|
|
3397
|
+
retVal = $scope.dataEventFunctions[schemaElm["params"]](record);
|
|
2599
3398
|
}
|
|
2600
3399
|
}
|
|
2601
3400
|
}
|
|
@@ -2612,7 +3411,7 @@ var fng;
|
|
|
2612
3411
|
if (angular.isArray(theValue)) {
|
|
2613
3412
|
for (var i = theValue.length - 1; i >= 0; i--) {
|
|
2614
3413
|
var type = typeof theValue[i];
|
|
2615
|
-
if (type ===
|
|
3414
|
+
if (type === "undefined" || (type === "object" && Object.keys(theValue[i]).length === 0)) {
|
|
2616
3415
|
theValue.splice(i, 1);
|
|
2617
3416
|
}
|
|
2618
3417
|
}
|
|
@@ -2632,16 +3431,35 @@ var fng;
|
|
|
2632
3431
|
}
|
|
2633
3432
|
}
|
|
2634
3433
|
}
|
|
3434
|
+
// Set up the lookup lists (value and id) on the scope for an internal lookup. Called by convertToAngularModel and $watch
|
|
3435
|
+
function setUpInternalLookupLists($scope, options, ids, newVal, valueAttrib) {
|
|
3436
|
+
var optionsArray = (typeof options === "string" ? $scope[options] : options);
|
|
3437
|
+
var idsArray = (typeof ids === "string" ? $scope[ids] : ids);
|
|
3438
|
+
optionsArray.length = 0;
|
|
3439
|
+
idsArray.length = 0;
|
|
3440
|
+
if (!!newVal && (newVal.length > 0)) {
|
|
3441
|
+
newVal.forEach(function (a) {
|
|
3442
|
+
var value = a[valueAttrib];
|
|
3443
|
+
if (value && value.length > 0) {
|
|
3444
|
+
optionsArray.push(value);
|
|
3445
|
+
if (!a._id) {
|
|
3446
|
+
a._id = makeMongoId();
|
|
3447
|
+
}
|
|
3448
|
+
idsArray.push(a._id);
|
|
3449
|
+
}
|
|
3450
|
+
});
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
2635
3453
|
var simpleArrayNeedsX = function (aSchema) {
|
|
2636
3454
|
var result = false;
|
|
2637
3455
|
if (aSchema.needsX) {
|
|
2638
3456
|
result = true;
|
|
2639
3457
|
}
|
|
2640
3458
|
else if (!aSchema.directive) {
|
|
2641
|
-
if (aSchema.type ===
|
|
3459
|
+
if (aSchema.type === "text") {
|
|
2642
3460
|
result = true;
|
|
2643
3461
|
}
|
|
2644
|
-
else if (aSchema.type ===
|
|
3462
|
+
else if (aSchema.type === "select" && !aSchema.ids) {
|
|
2645
3463
|
result = true;
|
|
2646
3464
|
}
|
|
2647
3465
|
}
|
|
@@ -2651,7 +3469,7 @@ var fng;
|
|
|
2651
3469
|
function getConversionObject(scope, entryName, schemaName) {
|
|
2652
3470
|
var conversions = scope.conversions;
|
|
2653
3471
|
if (schemaName) {
|
|
2654
|
-
conversions = conversions
|
|
3472
|
+
conversions = getData(conversions, schemaName) || {};
|
|
2655
3473
|
}
|
|
2656
3474
|
return conversions[entryName];
|
|
2657
3475
|
}
|
|
@@ -2662,43 +3480,62 @@ var fng;
|
|
|
2662
3480
|
for (var i = 0; i < schema.length; i++) {
|
|
2663
3481
|
var schemaEntry = schema[i];
|
|
2664
3482
|
var fieldName = schemaEntry.name.slice(prefixLength);
|
|
3483
|
+
if (!fieldName.length) {
|
|
3484
|
+
fieldName = schemaEntry.name.split('.').pop();
|
|
3485
|
+
}
|
|
2665
3486
|
var fieldValue = getData(anObject, fieldName);
|
|
3487
|
+
if (schemaEntry.intType === 'date' && typeof fieldValue === 'string') {
|
|
3488
|
+
setData(anObject, fieldName, null, new Date(fieldValue));
|
|
3489
|
+
}
|
|
2666
3490
|
if (schemaEntry.schema) {
|
|
2667
3491
|
if (fieldValue) {
|
|
2668
3492
|
for (var j = 0; j < fieldValue.length; j++) {
|
|
2669
|
-
fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j],
|
|
3493
|
+
fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j], 1 + fieldName.length, $scope, fieldName, master, j);
|
|
2670
3494
|
}
|
|
2671
3495
|
}
|
|
2672
3496
|
}
|
|
2673
3497
|
else {
|
|
3498
|
+
if (schemaEntry.internalRef) {
|
|
3499
|
+
setUpInternalLookupLists($scope, schemaEntry.options, schemaEntry.ids, master[schemaEntry.internalRef.property], schemaEntry.internalRef.value);
|
|
3500
|
+
}
|
|
2674
3501
|
// Convert {array:['item 1']} to {array:[{x:'item 1'}]}
|
|
2675
|
-
var thisField = getListData(
|
|
2676
|
-
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
|
+
) {
|
|
2677
3508
|
for (var k = 0; k < thisField.length; k++) {
|
|
2678
3509
|
thisField[k] = { x: thisField[k] };
|
|
2679
3510
|
}
|
|
2680
3511
|
}
|
|
2681
3512
|
// Convert {lookup:'012abcde'} to {lookup:'List description for 012abcde'}
|
|
2682
|
-
var idList = $scope[suffixCleanId(schemaEntry,
|
|
3513
|
+
var idList = $scope[suffixCleanId(schemaEntry, "_ids")];
|
|
2683
3514
|
var thisConversion = void 0;
|
|
2684
3515
|
if (fieldValue && idList && idList.length > 0) {
|
|
2685
|
-
if (
|
|
2686
|
-
|
|
2687
|
-
|
|
2688
|
-
|
|
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
|
+
}
|
|
2689
3525
|
}
|
|
2690
3526
|
else if (schemaEntry.select2) {
|
|
2691
3527
|
// Do nothing with these - handled elsewhere (and deprecated)
|
|
2692
|
-
console.log(
|
|
3528
|
+
console.log("fng-select2 is deprecated - use fng-ui-select instead");
|
|
2693
3529
|
void (schemaEntry.select2);
|
|
2694
3530
|
}
|
|
2695
3531
|
else if (fieldValue && (thisConversion = getConversionObject($scope, fieldName, schemaName)) &&
|
|
2696
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
|
|
2697
3534
|
!thisConversion.noconvert) {
|
|
2698
3535
|
thisConversion.fngajax(fieldValue, schemaEntry, function (updateEntry, value) {
|
|
2699
3536
|
// Update the master and (preserving pristine if appropriate) the record
|
|
2700
3537
|
setData(master, updateEntry.name, offset, value);
|
|
2701
|
-
preservePristine(angular.element(
|
|
3538
|
+
preservePristine(angular.element("#" + updateEntry.id), function () {
|
|
2702
3539
|
setData($scope.record, updateEntry.name, offset, value);
|
|
2703
3540
|
});
|
|
2704
3541
|
});
|
|
@@ -2711,9 +3548,9 @@ var fng;
|
|
|
2711
3548
|
// Called when the model is read and when the lookups are read
|
|
2712
3549
|
// No support for nested schemas here as it is called from convertToAngularModel which does that
|
|
2713
3550
|
function convertForeignKeys(schemaElement, input, values, ids) {
|
|
2714
|
-
if (schemaElement.array) {
|
|
3551
|
+
if (schemaElement.array || angular.isArray(input)) {
|
|
2715
3552
|
var returnArray = [];
|
|
2716
|
-
var needsX = !schemaElement.directive || simpleArrayNeedsX(schemaElement);
|
|
3553
|
+
var needsX = schemaElement.array && (!schemaElement.directive || simpleArrayNeedsX(schemaElement));
|
|
2717
3554
|
for (var j = 0; j < input.length; j++) {
|
|
2718
3555
|
var val = input[j];
|
|
2719
3556
|
if (val && val.x) {
|
|
@@ -2757,7 +3594,7 @@ var fng;
|
|
|
2757
3594
|
else {
|
|
2758
3595
|
var index = valuesArray.indexOf(textToConvert);
|
|
2759
3596
|
if (index === -1) {
|
|
2760
|
-
throw new Error(
|
|
3597
|
+
throw new Error("convertListValueToId: Invalid data - value " + textToConvert + " not found in " + valuesArray + " processing " + fname);
|
|
2761
3598
|
}
|
|
2762
3599
|
return idsArray[index];
|
|
2763
3600
|
}
|
|
@@ -2765,7 +3602,7 @@ var fng;
|
|
|
2765
3602
|
var preservePristine = function preservePristine(element, fn) {
|
|
2766
3603
|
// stop the form being set to dirty when a fn is called
|
|
2767
3604
|
// Use when the record (and master) need to be updated by lookup values displayed asynchronously
|
|
2768
|
-
var modelController = element.inheritedData(
|
|
3605
|
+
var modelController = element.inheritedData("$ngModelController");
|
|
2769
3606
|
var isClean = (modelController && modelController.$pristine);
|
|
2770
3607
|
if (isClean) {
|
|
2771
3608
|
// fake it to dirty here and reset after call to fn
|
|
@@ -2777,49 +3614,203 @@ var fng;
|
|
|
2777
3614
|
}
|
|
2778
3615
|
};
|
|
2779
3616
|
var convertIdToListValue = function convertIdToListValue(id, idsArray, valuesArray, fname) {
|
|
2780
|
-
if (typeof (id) ===
|
|
3617
|
+
if (typeof (id) === "object") {
|
|
2781
3618
|
id = id.id;
|
|
2782
3619
|
}
|
|
2783
3620
|
var index = idsArray.indexOf(id);
|
|
2784
3621
|
if (index === -1) {
|
|
2785
|
-
|
|
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
|
+
}
|
|
2786
3626
|
}
|
|
2787
3627
|
return valuesArray[index];
|
|
2788
3628
|
};
|
|
2789
3629
|
var processServerData = function processServerData(recordFromServer, $scope, ctrlState) {
|
|
2790
3630
|
ctrlState.master = convertToAngularModel($scope.formSchema, recordFromServer, 0, $scope);
|
|
2791
|
-
$scope.phase =
|
|
3631
|
+
$scope.phase = "ready";
|
|
2792
3632
|
$scope.cancel();
|
|
2793
3633
|
};
|
|
3634
|
+
function convertOldToNew(ref, val, attrib, newVals, oldVals) {
|
|
3635
|
+
// check this is a change to an existing value, rather than a new one or one being deleted
|
|
3636
|
+
if (oldVals && oldVals.length > 0 && oldVals.length === newVals.length && val[attrib]) {
|
|
3637
|
+
var index = oldVals.findIndex(function (a) { return a[ref.value] === val[attrib]; });
|
|
3638
|
+
if (index > -1) {
|
|
3639
|
+
var newVal = newVals[index][ref.value];
|
|
3640
|
+
if (newVal) {
|
|
3641
|
+
val[attrib] = newVal;
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3644
|
+
}
|
|
3645
|
+
}
|
|
2794
3646
|
function fillFormFromBackendCustomSchema(schema, $scope, formGeneratorInstance, recordHandlerInstance, ctrlState) {
|
|
2795
3647
|
var listOnly = (!$scope.id && !$scope.newRecord);
|
|
2796
3648
|
// passing null for formSchema parameter prevents all the work being done when we are just after the list data,
|
|
2797
3649
|
// but should be removed when/if formschemas are cached
|
|
2798
|
-
formGeneratorInstance.handleSchema(
|
|
3650
|
+
formGeneratorInstance.handleSchema("Main " + $scope.modelName, schema, listOnly ? null : $scope.formSchema, $scope.listSchema, "", true, $scope, ctrlState);
|
|
3651
|
+
function processLookupHandlers(newValue, oldValue) {
|
|
3652
|
+
// If we have any internal lookups then update the references
|
|
3653
|
+
$scope.internalLookups.forEach(function (lkp) {
|
|
3654
|
+
var newVal = newValue[lkp.ref.property];
|
|
3655
|
+
var oldVal = oldValue[lkp.ref.property];
|
|
3656
|
+
setUpInternalLookupLists($scope, lkp.lookupOptions, lkp.lookupIds, newVal, lkp.ref.value);
|
|
3657
|
+
// now change the looked-up values that matched the old to the new
|
|
3658
|
+
if ((newVal && newVal.length > 0) || (oldVal && oldVal.length > 0)) {
|
|
3659
|
+
lkp.handlers.forEach(function (h) {
|
|
3660
|
+
if (h.possibleArray) {
|
|
3661
|
+
var arr = getData($scope.record, h.possibleArray, null);
|
|
3662
|
+
if (arr && arr.length > 0) {
|
|
3663
|
+
arr.forEach(function (a) { return convertOldToNew(lkp.ref, a, h.lastPart, newVal, oldVal); });
|
|
3664
|
+
}
|
|
3665
|
+
}
|
|
3666
|
+
else if (angular.isArray($scope.record[h.lastPart])) {
|
|
3667
|
+
$scope.record[h.lastPart].forEach(function (a) {
|
|
3668
|
+
convertOldToNew(lkp.ref, a, "x", newVal, oldVal);
|
|
3669
|
+
});
|
|
3670
|
+
}
|
|
3671
|
+
else {
|
|
3672
|
+
convertOldToNew(lkp.ref, $scope.record, h.lastPart, newVal, oldVal);
|
|
3673
|
+
}
|
|
3674
|
+
});
|
|
3675
|
+
}
|
|
3676
|
+
});
|
|
3677
|
+
// If we have any list lookups then update the references
|
|
3678
|
+
$scope.listLookups.forEach(function (lkp) {
|
|
3679
|
+
function extractIdVal(obj, idString) {
|
|
3680
|
+
var retVal = obj[idString];
|
|
3681
|
+
if (retVal && retVal.id) {
|
|
3682
|
+
retVal = retVal.id;
|
|
3683
|
+
}
|
|
3684
|
+
return retVal;
|
|
3685
|
+
}
|
|
3686
|
+
function blankListLookup(inst) {
|
|
3687
|
+
setData($scope.record, inst.name);
|
|
3688
|
+
}
|
|
3689
|
+
var idString = lkp.ref.id.slice(1);
|
|
3690
|
+
if (idString.includes(".")) {
|
|
3691
|
+
throw new Error("No support for nested list lookups yet - ".concat(JSON.stringify(lkp.ref)));
|
|
3692
|
+
}
|
|
3693
|
+
var newVal = extractIdVal(newValue, idString);
|
|
3694
|
+
var oldVal = extractIdVal(oldValue, idString);
|
|
3695
|
+
if (newVal !== oldVal) {
|
|
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
|
+
}
|
|
3710
|
+
SubmissionsService.readRecord(lkp.ref.collection, newVal).then(function (response) {
|
|
3711
|
+
lkp.handlers.forEach(function (h) {
|
|
3712
|
+
var optionsList = $scope[h.formInstructions.options];
|
|
3713
|
+
optionsList.length = 0;
|
|
3714
|
+
var idList = $scope[h.formInstructions.ids];
|
|
3715
|
+
idList.length = 0;
|
|
3716
|
+
var data = response.data[lkp.ref.property] || [];
|
|
3717
|
+
for (var i = 0; i < data.length; i++) {
|
|
3718
|
+
var option = data[i][lkp.ref.value];
|
|
3719
|
+
var pos = _.sortedIndex(optionsList, option);
|
|
3720
|
+
// handle dupes
|
|
3721
|
+
if (optionsList[pos] === option) {
|
|
3722
|
+
option = option + " (" + data[i]._id + ")";
|
|
3723
|
+
pos = _.sortedIndex(optionsList, option);
|
|
3724
|
+
}
|
|
3725
|
+
optionsList.splice(pos, 0, option);
|
|
3726
|
+
idList.splice(pos, 0, data[i]._id);
|
|
3727
|
+
}
|
|
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
|
+
}
|
|
3755
|
+
});
|
|
3756
|
+
});
|
|
3757
|
+
}
|
|
3758
|
+
else {
|
|
3759
|
+
lkp.handlers.forEach(function (h) {
|
|
3760
|
+
$scope[h.formInstructions.options].length = 0;
|
|
3761
|
+
$scope[h.formInstructions.ids].length = 0;
|
|
3762
|
+
blankListLookup(h.formInstructions);
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
3765
|
+
}
|
|
3766
|
+
});
|
|
3767
|
+
}
|
|
3768
|
+
function notifyReady() {
|
|
3769
|
+
$scope.phase = "ready";
|
|
3770
|
+
$scope.cancel();
|
|
3771
|
+
processLookupHandlers($scope.record, {});
|
|
3772
|
+
}
|
|
2799
3773
|
if (listOnly) {
|
|
2800
3774
|
ctrlState.allowLocationChange = true;
|
|
2801
3775
|
}
|
|
2802
3776
|
else {
|
|
2803
3777
|
var force = true;
|
|
2804
3778
|
if (!$scope.newRecord) {
|
|
2805
|
-
$scope.dropConversionWatcher = $scope.$watchCollection(
|
|
3779
|
+
$scope.dropConversionWatcher = $scope.$watchCollection("conversions", function (newValue, oldValue) {
|
|
2806
3780
|
if (newValue !== oldValue && $scope.originalData) {
|
|
2807
3781
|
processServerData($scope.originalData, $scope, ctrlState);
|
|
2808
3782
|
}
|
|
2809
3783
|
});
|
|
2810
3784
|
}
|
|
2811
|
-
$scope.$watch(
|
|
3785
|
+
$scope.$watch("record", function (newValue, oldValue) {
|
|
2812
3786
|
if (newValue !== oldValue) {
|
|
2813
3787
|
if (Object.keys(oldValue).length > 0 && $scope.dropConversionWatcher) {
|
|
2814
3788
|
$scope.dropConversionWatcher(); // Don't want to convert changed data
|
|
2815
3789
|
$scope.dropConversionWatcher = null;
|
|
2816
3790
|
}
|
|
2817
3791
|
force = formGeneratorInstance.updateDataDependentDisplay(newValue, oldValue, force, $scope);
|
|
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
|
+
}
|
|
2818
3809
|
}
|
|
2819
3810
|
}, true);
|
|
2820
3811
|
if ($scope.id) {
|
|
2821
3812
|
// Going to read a record
|
|
2822
|
-
if (typeof $scope.dataEventFunctions.onBeforeRead ===
|
|
3813
|
+
if (typeof $scope.dataEventFunctions.onBeforeRead === "function") {
|
|
2823
3814
|
$scope.dataEventFunctions.onBeforeRead($scope.id, function (err) {
|
|
2824
3815
|
if (err) {
|
|
2825
3816
|
$scope.showError(err);
|
|
@@ -2835,99 +3826,130 @@ var fng;
|
|
|
2835
3826
|
}
|
|
2836
3827
|
else {
|
|
2837
3828
|
// New record
|
|
2838
|
-
ctrlState.
|
|
3829
|
+
ctrlState.allowLocationChange = false;
|
|
3830
|
+
ctrlState.master = $scope.setDefaults($scope.formSchema);
|
|
2839
3831
|
var passedRecord = $scope.initialiseNewRecord || $location.$$search.r;
|
|
2840
3832
|
if (passedRecord) {
|
|
2841
3833
|
try {
|
|
2842
|
-
ctrlState.master
|
|
2843
|
-
|
|
2844
|
-
|
|
2845
|
-
|
|
2846
|
-
|
|
2847
|
-
$scope[$scope.topLevelFormName]
|
|
2848
|
-
|
|
2849
|
-
|
|
2850
|
-
|
|
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
|
+
}
|
|
2851
3845
|
}
|
|
2852
3846
|
catch (e) {
|
|
2853
|
-
console.log(
|
|
3847
|
+
console.log("Error parsing specified record : " + e.message);
|
|
2854
3848
|
}
|
|
2855
3849
|
}
|
|
2856
|
-
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)");
|
|
2857
3852
|
$scope.dataEventFunctions.onInitialiseNewRecord(ctrlState.master);
|
|
2858
3853
|
}
|
|
2859
|
-
$scope.
|
|
2860
|
-
|
|
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
|
+
}
|
|
2861
3867
|
}
|
|
2862
3868
|
}
|
|
2863
3869
|
}
|
|
2864
3870
|
function handleError($scope) {
|
|
2865
3871
|
return function (response) {
|
|
2866
|
-
if ([200, 400].indexOf(response.status) !== -1) {
|
|
2867
|
-
var errorMessage =
|
|
2868
|
-
|
|
2869
|
-
|
|
2870
|
-
|
|
2871
|
-
|
|
2872
|
-
|
|
2873
|
-
|
|
2874
|
-
|
|
2875
|
-
|
|
2876
|
-
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
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
|
+
}
|
|
2880
3888
|
}
|
|
2881
3889
|
}
|
|
2882
3890
|
if (errorMessage.length > 0) {
|
|
2883
|
-
errorMessage = response.data.message +
|
|
3891
|
+
errorMessage = (response.data.message || response.data._message) + "<br /><ul>" + errorMessage + "</ul>";
|
|
2884
3892
|
}
|
|
2885
3893
|
else {
|
|
2886
|
-
errorMessage = response.data.message ||
|
|
3894
|
+
errorMessage = response.data.message || response.data._message || response.data.err || "Error! Sorry - No further details available.";
|
|
2887
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";
|
|
2888
3899
|
$scope.showError(errorMessage);
|
|
2889
3900
|
}
|
|
2890
3901
|
else {
|
|
2891
|
-
$scope.showError(response.status +
|
|
3902
|
+
$scope.showError(response.status + " " + JSON.stringify(response.data));
|
|
2892
3903
|
}
|
|
2893
3904
|
};
|
|
2894
3905
|
}
|
|
2895
3906
|
function handleIncomingData(data, $scope, ctrlState) {
|
|
2896
3907
|
ctrlState.allowLocationChange = false;
|
|
2897
|
-
$scope.phase =
|
|
2898
|
-
if (typeof $scope.dataEventFunctions.onAfterRead ===
|
|
3908
|
+
$scope.phase = "reading";
|
|
3909
|
+
if (typeof $scope.dataEventFunctions.onAfterRead === "function") {
|
|
2899
3910
|
$scope.dataEventFunctions.onAfterRead(data);
|
|
2900
3911
|
}
|
|
2901
3912
|
$scope.originalData = data;
|
|
2902
3913
|
processServerData(data, $scope, ctrlState);
|
|
2903
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
|
+
}
|
|
2904
3938
|
return {
|
|
2905
3939
|
readRecord: function readRecord($scope, ctrlState) {
|
|
2906
|
-
|
|
2907
|
-
|
|
3940
|
+
$scope.readingRecord = SubmissionsService.readRecord($scope.modelName, $scope.id, $scope.formName);
|
|
3941
|
+
$scope.readingRecord
|
|
2908
3942
|
.then(function (response) {
|
|
2909
|
-
var data = response.data;
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
|
|
2914
|
-
//
|
|
2915
|
-
// ctrlState.allowLocationChange = false;
|
|
2916
|
-
// $scope.phase = 'ready';
|
|
2917
|
-
// $scope.record = angular.copy(response.data);
|
|
2918
|
-
// ctrlState.master = angular.copy(response.master);
|
|
2919
|
-
// if (response.changed) {
|
|
2920
|
-
// $timeout(() => {
|
|
2921
|
-
// $scope[$scope.topLevelFormName].$setDirty();
|
|
2922
|
-
// });
|
|
2923
|
-
// } else {
|
|
2924
|
-
// $timeout($scope.setPristine);
|
|
2925
|
-
// }
|
|
3943
|
+
var data = angular.copy(response.data);
|
|
3944
|
+
handleIncomingData(data, $scope, ctrlState);
|
|
3945
|
+
}, function (error) {
|
|
3946
|
+
if (error.status === 404) {
|
|
3947
|
+
$location.path("/404");
|
|
2926
3948
|
}
|
|
2927
3949
|
else {
|
|
2928
|
-
|
|
3950
|
+
$scope.handleHttpError(error);
|
|
2929
3951
|
}
|
|
2930
|
-
}
|
|
3952
|
+
});
|
|
2931
3953
|
},
|
|
2932
3954
|
scrollTheList: function scrollTheList($scope) {
|
|
2933
3955
|
var pagesLoaded = $scope.pagesLoaded;
|
|
@@ -2936,42 +3958,64 @@ var fng;
|
|
|
2936
3958
|
find: $location.$$search.f,
|
|
2937
3959
|
limit: $scope.pageSize,
|
|
2938
3960
|
skip: pagesLoaded * $scope.pageSize,
|
|
2939
|
-
order: $location.$$search.o
|
|
3961
|
+
order: $location.$$search.o,
|
|
3962
|
+
concatenate: false
|
|
2940
3963
|
})
|
|
2941
3964
|
.then(function (response) {
|
|
2942
3965
|
var data = response.data;
|
|
2943
3966
|
if (angular.isArray(data)) {
|
|
2944
|
-
//
|
|
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
|
|
2945
3977
|
if (pagesLoaded === $scope.pagesLoaded) {
|
|
2946
3978
|
$scope.pagesLoaded++;
|
|
2947
3979
|
$scope.recordList = $scope.recordList.concat(data);
|
|
2948
3980
|
}
|
|
2949
3981
|
else {
|
|
2950
|
-
console.log(
|
|
3982
|
+
console.log("DEBUG: infinite scroll component asked for a page twice - the model was " + $scope.modelName);
|
|
2951
3983
|
}
|
|
2952
3984
|
}
|
|
2953
3985
|
else {
|
|
2954
|
-
$scope.showError(data,
|
|
3986
|
+
$scope.showError(data, "Invalid query");
|
|
2955
3987
|
}
|
|
2956
3988
|
}, $scope.handleHttpError);
|
|
2957
3989
|
},
|
|
2958
|
-
|
|
2959
|
-
|
|
2960
|
-
SubmissionsService.deleteRecord(
|
|
3990
|
+
deleteRecord: function deleteRecord(id, $scope, ctrlState) {
|
|
3991
|
+
$scope.phase = "deleting";
|
|
3992
|
+
SubmissionsService.deleteRecord($scope.modelName, id)
|
|
2961
3993
|
.then(function () {
|
|
2962
|
-
if (typeof $scope.dataEventFunctions.onAfterDelete ===
|
|
3994
|
+
if (typeof $scope.dataEventFunctions.onAfterDelete === "function") {
|
|
2963
3995
|
$scope.dataEventFunctions.onAfterDelete(ctrlState.master);
|
|
2964
3996
|
}
|
|
2965
|
-
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
|
+
}
|
|
2966
4010
|
});
|
|
2967
4011
|
},
|
|
2968
4012
|
updateDocument: function updateDocument(dataToSave, options, $scope, ctrlState) {
|
|
2969
|
-
$scope.phase =
|
|
4013
|
+
$scope.phase = "updating";
|
|
2970
4014
|
SubmissionsService.updateRecord($scope.modelName, $scope.id, dataToSave)
|
|
2971
4015
|
.then(function (response) {
|
|
2972
4016
|
var data = response.data;
|
|
2973
4017
|
if (data.success !== false) {
|
|
2974
|
-
if (typeof $scope.dataEventFunctions.onAfterUpdate ===
|
|
4018
|
+
if (typeof $scope.dataEventFunctions.onAfterUpdate === "function") {
|
|
2975
4019
|
$scope.dataEventFunctions.onAfterUpdate(data, ctrlState.master);
|
|
2976
4020
|
}
|
|
2977
4021
|
if (options.redirect) {
|
|
@@ -2988,21 +4032,24 @@ var fng;
|
|
|
2988
4032
|
else {
|
|
2989
4033
|
$scope.showError(data);
|
|
2990
4034
|
}
|
|
2991
|
-
},
|
|
4035
|
+
}, function (err) {
|
|
4036
|
+
$scope.handleHttpError(err);
|
|
4037
|
+
});
|
|
2992
4038
|
},
|
|
2993
|
-
createNew: function createNew(dataToSave, options, $scope) {
|
|
4039
|
+
createNew: function createNew(dataToSave, options, $scope, ctrlState) {
|
|
2994
4040
|
SubmissionsService.createRecord($scope.modelName, dataToSave)
|
|
2995
4041
|
.then(function (response) {
|
|
2996
4042
|
var data = response.data;
|
|
2997
4043
|
if (data.success !== false) {
|
|
2998
|
-
|
|
4044
|
+
ctrlState.allowLocationChange = true;
|
|
4045
|
+
if (typeof $scope.dataEventFunctions.onAfterCreate === "function") {
|
|
2999
4046
|
$scope.dataEventFunctions.onAfterCreate(data);
|
|
3000
4047
|
}
|
|
3001
4048
|
if (options.redirect) {
|
|
3002
4049
|
$window.location = options.redirect;
|
|
3003
4050
|
}
|
|
3004
4051
|
else {
|
|
3005
|
-
routingService.redirectTo()(
|
|
4052
|
+
routingService.redirectTo()("edit", $scope, $location, data._id);
|
|
3006
4053
|
}
|
|
3007
4054
|
}
|
|
3008
4055
|
else {
|
|
@@ -3012,47 +4059,78 @@ var fng;
|
|
|
3012
4059
|
},
|
|
3013
4060
|
getListData: getListData,
|
|
3014
4061
|
suffixCleanId: suffixCleanId,
|
|
4062
|
+
getData: getData,
|
|
3015
4063
|
setData: setData,
|
|
3016
|
-
|
|
4064
|
+
setUpLookupOptions: function setUpLookupOptions(lookupCollection, schemaElement, $scope, ctrlState, handleSchema) {
|
|
3017
4065
|
var optionsList = $scope[schemaElement.options] = [];
|
|
3018
4066
|
var idList = $scope[schemaElement.ids] = [];
|
|
3019
|
-
|
|
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
|
|
3020
4071
|
.then(function (response) {
|
|
3021
|
-
var
|
|
3022
|
-
|
|
3023
|
-
|
|
3024
|
-
|
|
3025
|
-
|
|
3026
|
-
|
|
3027
|
-
|
|
3028
|
-
|
|
3029
|
-
|
|
3030
|
-
|
|
3031
|
-
dataRequest
|
|
3032
|
-
.then(function (response) {
|
|
3033
|
-
var data = response.data;
|
|
3034
|
-
if (data) {
|
|
3035
|
-
for (var i = 0; i < data.length; i++) {
|
|
3036
|
-
var option = '';
|
|
3037
|
-
for (var j = 0; j < listInstructions.length; j++) {
|
|
3038
|
-
var thisVal = data[i][listInstructions[j].name];
|
|
3039
|
-
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]);
|
|
3040
4082
|
}
|
|
3041
|
-
|
|
3042
|
-
|
|
3043
|
-
|
|
3044
|
-
|
|
3045
|
-
|
|
3046
|
-
|
|
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] + ")";
|
|
3047
4090
|
}
|
|
3048
|
-
optionsList.splice(pos, 0, option);
|
|
3049
|
-
idList.splice(pos, 0, data[i]._id);
|
|
3050
4091
|
}
|
|
3051
|
-
|
|
4092
|
+
});
|
|
4093
|
+
if ($scope.readingRecord) {
|
|
4094
|
+
$scope.readingRecord
|
|
4095
|
+
.then(function () {
|
|
4096
|
+
updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
|
|
4097
|
+
});
|
|
3052
4098
|
}
|
|
3053
|
-
}
|
|
4099
|
+
}
|
|
4100
|
+
})
|
|
4101
|
+
.catch(function (e) {
|
|
4102
|
+
$scope.handleHttpError(e);
|
|
3054
4103
|
});
|
|
3055
4104
|
},
|
|
4105
|
+
setUpLookupListOptions: function setUpLookupListOptions(ref, formInstructions, $scope, ctrlState) {
|
|
4106
|
+
var optionsList = $scope[formInstructions.options] = [];
|
|
4107
|
+
var idList = $scope[formInstructions.ids] = [];
|
|
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);
|
|
4111
|
+
}
|
|
4112
|
+
else {
|
|
4113
|
+
// we can do it now
|
|
4114
|
+
SubmissionsService.readRecord(ref.collection, $scope.$eval(ref.id)).then(function (response) {
|
|
4115
|
+
var data = response.data[ref.property];
|
|
4116
|
+
for (var i = 0; i < data.length; i++) {
|
|
4117
|
+
var option = data[i][ref.value];
|
|
4118
|
+
var pos = _.sortedIndex(optionsList, option);
|
|
4119
|
+
// handle dupes
|
|
4120
|
+
if (optionsList[pos] === option) {
|
|
4121
|
+
option = option + " (" + data[i]._id + ")";
|
|
4122
|
+
pos = _.sortedIndex(optionsList, option);
|
|
4123
|
+
}
|
|
4124
|
+
optionsList.splice(pos, 0, option);
|
|
4125
|
+
idList.splice(pos, 0, data[i]._id);
|
|
4126
|
+
}
|
|
4127
|
+
updateRecordWithLookupValues(formInstructions, $scope, ctrlState);
|
|
4128
|
+
});
|
|
4129
|
+
}
|
|
4130
|
+
},
|
|
4131
|
+
handleInternalLookup: function handleInternalLookup($scope, formInstructions, ref) {
|
|
4132
|
+
addArrayLookupToLookupList($scope, formInstructions, ref, $scope.internalLookups);
|
|
4133
|
+
},
|
|
3056
4134
|
preservePristine: preservePristine,
|
|
3057
4135
|
// Reverse the process of convertToAngularModel
|
|
3058
4136
|
convertToMongoModel: function convertToMongoModel(schema, anObject, prefixLength, $scope, schemaName) {
|
|
@@ -3068,48 +4146,54 @@ var fng;
|
|
|
3068
4146
|
}
|
|
3069
4147
|
return retVal;
|
|
3070
4148
|
}
|
|
3071
|
-
|
|
3072
|
-
var
|
|
3073
|
-
var
|
|
3074
|
-
|
|
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) {
|
|
3075
4154
|
if (thisField) {
|
|
3076
4155
|
for (var j = 0; j < thisField.length; j++) {
|
|
3077
|
-
thisField[j] = convertToMongoModel(
|
|
4156
|
+
thisField[j] = convertToMongoModel(schemaI.schema, thisField[j], 1 + fieldname.length, $scope, fieldname);
|
|
3078
4157
|
}
|
|
3079
4158
|
}
|
|
3080
4159
|
}
|
|
3081
4160
|
else {
|
|
3082
4161
|
// Convert {array:[{x:'item 1'}]} to {array:['item 1']}
|
|
3083
|
-
if (
|
|
4162
|
+
if (schemaI.array && simpleArrayNeedsX(schemaI) && thisField) {
|
|
3084
4163
|
for (var k = 0; k < thisField.length; k++) {
|
|
3085
4164
|
thisField[k] = thisField[k].x;
|
|
3086
4165
|
}
|
|
3087
4166
|
}
|
|
3088
4167
|
// Convert {lookup:'List description for 012abcde'} to {lookup:'012abcde'}
|
|
3089
|
-
var
|
|
3090
|
-
|
|
3091
|
-
if (idList && idList.length > 0) {
|
|
4168
|
+
var idList_1 = $scope[suffixCleanId(schemaI, "_ids")];
|
|
4169
|
+
if (idList_1 && idList_1.length > 0) {
|
|
3092
4170
|
updateObject(fieldname, anObject, function (value) {
|
|
3093
|
-
return convertToForeignKeys(
|
|
4171
|
+
return convertToForeignKeys(schemaI, value, $scope[suffixCleanId(schemaI, "Options")], idList_1);
|
|
3094
4172
|
});
|
|
3095
4173
|
}
|
|
3096
|
-
else
|
|
3097
|
-
var
|
|
3098
|
-
|
|
3099
|
-
|
|
3100
|
-
newVal =
|
|
3101
|
-
if (
|
|
3102
|
-
|
|
3103
|
-
|
|
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
|
+
}
|
|
3104
4185
|
}
|
|
3105
4186
|
}
|
|
4187
|
+
else {
|
|
4188
|
+
newVal = convertLookup(lookup, thisConversion);
|
|
4189
|
+
}
|
|
4190
|
+
setData(anObject, fieldname, null, newVal);
|
|
3106
4191
|
}
|
|
3107
|
-
else {
|
|
3108
|
-
newVal = convertLookup(lookup, thisConversion);
|
|
3109
|
-
}
|
|
3110
|
-
setData(anObject, fieldname, null, newVal);
|
|
3111
4192
|
}
|
|
3112
4193
|
}
|
|
4194
|
+
};
|
|
4195
|
+
for (var i = 0; i < schema.length; i++) {
|
|
4196
|
+
_loop_1();
|
|
3113
4197
|
}
|
|
3114
4198
|
return anObject;
|
|
3115
4199
|
},
|
|
@@ -3119,7 +4203,7 @@ var fng;
|
|
|
3119
4203
|
$scope.handleHttpError = handleError($scope);
|
|
3120
4204
|
$scope.cancel = function () {
|
|
3121
4205
|
angular.copy(ctrlState.master, $scope.record);
|
|
3122
|
-
$scope.$broadcast(
|
|
4206
|
+
$scope.$broadcast("fngCancel", $scope);
|
|
3123
4207
|
// Let call backs etc resolve in case they dirty form, then clean it
|
|
3124
4208
|
$timeout($scope.setPristine);
|
|
3125
4209
|
};
|
|
@@ -3128,15 +4212,24 @@ var fng;
|
|
|
3128
4212
|
// scope.$emit('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
|
|
3129
4213
|
// or
|
|
3130
4214
|
// scope.$broadcast('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
|
|
3131
|
-
$scope.$on(
|
|
3132
|
-
|
|
4215
|
+
$scope.$on("showErrorMessage", function (event, args) {
|
|
4216
|
+
if (!event.defaultPrevented) {
|
|
4217
|
+
event.defaultPrevented = true;
|
|
4218
|
+
$scope.showError(args.body, args.title);
|
|
4219
|
+
}
|
|
3133
4220
|
});
|
|
3134
4221
|
$scope.showError = function (error, alertTitle) {
|
|
3135
|
-
$scope.alertTitle = alertTitle ? alertTitle :
|
|
3136
|
-
|
|
3137
|
-
$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);
|
|
3138
4228
|
}
|
|
3139
|
-
else if (error
|
|
4229
|
+
else if (!error) {
|
|
4230
|
+
$scope.errorMessage = "An error occurred - that's all we got. Sorry.";
|
|
4231
|
+
}
|
|
4232
|
+
else if (error.message && typeof error.message === "string") {
|
|
3140
4233
|
$scope.errorMessage = error.message;
|
|
3141
4234
|
}
|
|
3142
4235
|
else if (error.data && error.data.message) {
|
|
@@ -3150,16 +4243,35 @@ var fng;
|
|
|
3150
4243
|
$scope.errorMessage = error;
|
|
3151
4244
|
}
|
|
3152
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
|
+
}
|
|
3153
4260
|
};
|
|
3154
4261
|
$scope.dismissError = function () {
|
|
4262
|
+
$scope.clearTimeout;
|
|
4263
|
+
$scope.errorVisible = false;
|
|
3155
4264
|
delete $scope.errorMessage;
|
|
3156
4265
|
delete $scope.alertTitle;
|
|
3157
4266
|
};
|
|
4267
|
+
$scope.stickError = function () {
|
|
4268
|
+
clearTimeout($scope.errorHideTimer);
|
|
4269
|
+
};
|
|
3158
4270
|
$scope.prepareForSave = function (cb) {
|
|
3159
4271
|
//Convert the lookup values into ids
|
|
3160
4272
|
var dataToSave = recordHandlerInstance.convertToMongoModel($scope.formSchema, angular.copy($scope.record), 0, $scope);
|
|
3161
4273
|
if ($scope.id) {
|
|
3162
|
-
if (typeof $scope.dataEventFunctions.onBeforeUpdate ===
|
|
4274
|
+
if (typeof $scope.dataEventFunctions.onBeforeUpdate === "function") {
|
|
3163
4275
|
$scope.dataEventFunctions.onBeforeUpdate(dataToSave, ctrlState.master, function (err) {
|
|
3164
4276
|
if (err) {
|
|
3165
4277
|
cb(err);
|
|
@@ -3174,7 +4286,7 @@ var fng;
|
|
|
3174
4286
|
}
|
|
3175
4287
|
}
|
|
3176
4288
|
else {
|
|
3177
|
-
if (typeof $scope.dataEventFunctions.onBeforeCreate ===
|
|
4289
|
+
if (typeof $scope.dataEventFunctions.onBeforeCreate === "function") {
|
|
3178
4290
|
$scope.dataEventFunctions.onBeforeCreate(dataToSave, function (err) {
|
|
3179
4291
|
if (err) {
|
|
3180
4292
|
cb(err);
|
|
@@ -3193,55 +4305,48 @@ var fng;
|
|
|
3193
4305
|
options = options || {};
|
|
3194
4306
|
$scope.prepareForSave(function (err, dataToSave) {
|
|
3195
4307
|
if (err) {
|
|
3196
|
-
if (err !==
|
|
3197
|
-
$
|
|
4308
|
+
if (err !== "_update_handled_") {
|
|
4309
|
+
$timeout(function () {
|
|
4310
|
+
$scope.showError(err);
|
|
4311
|
+
});
|
|
3198
4312
|
}
|
|
3199
4313
|
}
|
|
3200
4314
|
else if ($scope.id) {
|
|
3201
4315
|
recordHandlerInstance.updateDocument(dataToSave, options, $scope, ctrlState);
|
|
3202
4316
|
}
|
|
3203
4317
|
else {
|
|
3204
|
-
recordHandlerInstance.createNew(dataToSave, options, $scope);
|
|
4318
|
+
recordHandlerInstance.createNew(dataToSave, options, $scope, ctrlState);
|
|
3205
4319
|
}
|
|
3206
4320
|
});
|
|
3207
4321
|
};
|
|
3208
4322
|
$scope.newClick = function () {
|
|
3209
|
-
routingService.redirectTo()(
|
|
4323
|
+
routingService.redirectTo()("new", $scope, $location);
|
|
3210
4324
|
};
|
|
3211
|
-
$scope.$on(
|
|
3212
|
-
|
|
3213
|
-
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
do {
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3221
|
-
|
|
3222
|
-
} while (tabChangeOnly && curPath[i] !== 'edit');
|
|
3223
|
-
if (tabChangeOnly) {
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
}
|
|
3227
|
-
|
|
4325
|
+
$scope.$on("$locationChangeStart", function (event, next) {
|
|
4326
|
+
// let changed = !$scope.isCancelDisabled();
|
|
4327
|
+
// let curPath = window.location.href.split('/');
|
|
4328
|
+
// let nextPath = next.split('/');
|
|
4329
|
+
// let tabChangeOnly = true;
|
|
4330
|
+
// let i = 0;
|
|
4331
|
+
// do {
|
|
4332
|
+
// i += 1;
|
|
4333
|
+
// if (curPath[i] !== nextPath[i]) {
|
|
4334
|
+
// tabChangeOnly = false;
|
|
4335
|
+
// }
|
|
4336
|
+
// } while (tabChangeOnly && curPath[i] !== 'edit');
|
|
4337
|
+
// if (tabChangeOnly) {
|
|
4338
|
+
// // let dataToReturn = recordHandlerInstance.convertToMongoModel($scope.formSchema, angular.copy($scope.record), 0, $scope);
|
|
4339
|
+
// SubmissionsService.setUpForTabChange($scope.modelName, $scope.id, $scope.record, ctrlState.master, changed);
|
|
4340
|
+
// } else if (!ctrlState.allowLocationChange && changed) {
|
|
4341
|
+
if (!ctrlState.allowLocationChange && !$scope.isCancelDisabled()) {
|
|
3228
4342
|
event.preventDefault();
|
|
3229
4343
|
var modalInstance = $uibModal.open({
|
|
3230
|
-
template:
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
'<div class="modal-body">' +
|
|
3234
|
-
' <p>Would you like to save your changes?</p>' +
|
|
3235
|
-
'</div>' +
|
|
3236
|
-
'<div class="modal-footer">' +
|
|
3237
|
-
' <button class="btn btn-primary dlg-yes" ng-click="yes()">Yes</button>' +
|
|
3238
|
-
' <button class="btn btn-warning dlg-no" ng-click="no()">No</button>' +
|
|
3239
|
-
' <button class="btn dlg-cancel" ng-click="cancel()">Cancel</button>' +
|
|
3240
|
-
'</div>',
|
|
3241
|
-
controller: 'SaveChangesModalCtrl',
|
|
3242
|
-
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"
|
|
3243
4347
|
});
|
|
3244
|
-
modalInstance.result
|
|
4348
|
+
modalInstance.result
|
|
4349
|
+
.then(function (result) {
|
|
3245
4350
|
if (result) {
|
|
3246
4351
|
$scope.save({ redirect: next, allowChange: true }); // save changes
|
|
3247
4352
|
}
|
|
@@ -3249,7 +4354,8 @@ var fng;
|
|
|
3249
4354
|
ctrlState.allowLocationChange = true;
|
|
3250
4355
|
$window.location = next;
|
|
3251
4356
|
}
|
|
3252
|
-
})
|
|
4357
|
+
})
|
|
4358
|
+
.catch(_handleCancel);
|
|
3253
4359
|
}
|
|
3254
4360
|
});
|
|
3255
4361
|
$scope.deleteClick = function () {
|
|
@@ -3260,85 +4366,180 @@ var fng;
|
|
|
3260
4366
|
}
|
|
3261
4367
|
else {
|
|
3262
4368
|
var modalInstance = $uibModal.open({
|
|
3263
|
-
template:
|
|
3264
|
-
|
|
3265
|
-
|
|
3266
|
-
'<div class="modal-body">' +
|
|
3267
|
-
' <p>Are you sure you want to delete this record?</p>' +
|
|
3268
|
-
'</div>' +
|
|
3269
|
-
'<div class="modal-footer">' +
|
|
3270
|
-
' <button class="btn btn-primary dlg-no" ng-click="cancel()">No</button>' +
|
|
3271
|
-
' <button class="btn btn-warning dlg-yes" ng-click="yes()">Yes</button>' +
|
|
3272
|
-
'</div>',
|
|
3273
|
-
controller: 'SaveChangesModalCtrl',
|
|
3274
|
-
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"
|
|
3275
4372
|
});
|
|
3276
4373
|
confirmDelete = modalInstance.result;
|
|
3277
4374
|
}
|
|
3278
4375
|
confirmDelete.then(function (result) {
|
|
4376
|
+
function doTheDeletion() {
|
|
4377
|
+
recordHandlerInstance.deleteRecord($scope.id, $scope, ctrlState);
|
|
4378
|
+
}
|
|
3279
4379
|
if (result) {
|
|
3280
|
-
if (typeof $scope.dataEventFunctions.onBeforeDelete ===
|
|
4380
|
+
if (typeof $scope.dataEventFunctions.onBeforeDelete === "function") {
|
|
3281
4381
|
$scope.dataEventFunctions.onBeforeDelete(ctrlState.master, function (err) {
|
|
3282
4382
|
if (err) {
|
|
3283
|
-
if (err !==
|
|
4383
|
+
if (err !== "_delete_handled_") {
|
|
3284
4384
|
$scope.showError(err);
|
|
3285
4385
|
}
|
|
3286
4386
|
}
|
|
3287
4387
|
else {
|
|
3288
|
-
|
|
4388
|
+
doTheDeletion();
|
|
3289
4389
|
}
|
|
3290
4390
|
});
|
|
3291
4391
|
}
|
|
3292
4392
|
else {
|
|
3293
|
-
|
|
4393
|
+
doTheDeletion();
|
|
3294
4394
|
}
|
|
3295
4395
|
}
|
|
3296
|
-
})
|
|
4396
|
+
})
|
|
4397
|
+
.catch(_handleCancel);
|
|
3297
4398
|
}
|
|
3298
4399
|
};
|
|
3299
4400
|
$scope.isCancelDisabled = function () {
|
|
3300
|
-
|
|
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") {
|
|
3301
4406
|
return $scope.disableFunctions.isCancelDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
3302
4407
|
}
|
|
3303
4408
|
else {
|
|
3304
|
-
return
|
|
4409
|
+
return false;
|
|
3305
4410
|
}
|
|
3306
4411
|
};
|
|
3307
4412
|
$scope.isSaveDisabled = function () {
|
|
3308
|
-
|
|
3309
|
-
|
|
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
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
else {
|
|
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;
|
|
3310
4488
|
}
|
|
3311
4489
|
else {
|
|
3312
|
-
|
|
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;
|
|
3313
4498
|
}
|
|
3314
4499
|
};
|
|
3315
4500
|
$scope.isDeleteDisabled = function () {
|
|
3316
|
-
|
|
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") {
|
|
3317
4506
|
return $scope.disableFunctions.isDeleteDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
3318
4507
|
}
|
|
3319
4508
|
else {
|
|
3320
|
-
return
|
|
4509
|
+
return false;
|
|
3321
4510
|
}
|
|
3322
4511
|
};
|
|
3323
4512
|
$scope.isNewDisabled = function () {
|
|
3324
|
-
|
|
4513
|
+
var _a;
|
|
4514
|
+
if (typeof ((_a = $scope.disableFunctions) === null || _a === void 0 ? void 0 : _a.isNewDisabled) === "function") {
|
|
3325
4515
|
return $scope.disableFunctions.isNewDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
|
|
3326
4516
|
}
|
|
3327
4517
|
else {
|
|
3328
4518
|
return false;
|
|
3329
4519
|
}
|
|
3330
4520
|
};
|
|
3331
|
-
$scope.
|
|
3332
|
-
|
|
3333
|
-
|
|
3334
|
-
|
|
3335
|
-
|
|
3336
|
-
|
|
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;
|
|
3337
4538
|
};
|
|
3338
4539
|
$scope.getVal = function (expression, index) {
|
|
3339
|
-
if (expression.indexOf(
|
|
4540
|
+
if (expression.indexOf("$index") === -1 || typeof index !== "undefined") {
|
|
3340
4541
|
expression = expression.replace(/\$index/g, index);
|
|
3341
|
-
return $scope.$eval(
|
|
4542
|
+
return $scope.$eval("record." + expression);
|
|
3342
4543
|
}
|
|
3343
4544
|
//else {
|
|
3344
4545
|
// Used to show error here, but angular seems to call before record is populated sometimes
|
|
@@ -3346,11 +4547,42 @@ var fng;
|
|
|
3346
4547
|
//}
|
|
3347
4548
|
};
|
|
3348
4549
|
$scope.sortableOptions = {
|
|
3349
|
-
update: function () {
|
|
3350
|
-
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) {
|
|
3351
4564
|
$scope[$scope.topLevelFormName].$setDirty();
|
|
3352
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
|
+
}
|
|
3353
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);
|
|
3354
4586
|
};
|
|
3355
4587
|
},
|
|
3356
4588
|
fillFormFromBackendCustomSchema: fillFormFromBackendCustomSchema,
|
|
@@ -3389,17 +4621,267 @@ var fng;
|
|
|
3389
4621
|
var services;
|
|
3390
4622
|
(function (services) {
|
|
3391
4623
|
/*@ngInject*/
|
|
3392
|
-
|
|
3393
|
-
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();
|
|
3394
4874
|
/*
|
|
3395
4875
|
generate a query string for a filtered and paginated query for submissions.
|
|
3396
4876
|
options consists of the following:
|
|
3397
4877
|
{
|
|
3398
4878
|
aggregate - whether or not to aggregate results (http://docs.mongodb.org/manual/aggregation/)
|
|
3399
4879
|
find - find parameter
|
|
4880
|
+
projection - the fields to return
|
|
3400
4881
|
limit - limit results to this number of records
|
|
3401
4882
|
skip - skip this number of records before returning results
|
|
3402
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)
|
|
3403
4885
|
}
|
|
3404
4886
|
*/
|
|
3405
4887
|
var generateListQuery = function (options) {
|
|
@@ -3420,9 +4902,11 @@ var fng;
|
|
|
3420
4902
|
};
|
|
3421
4903
|
addParameter('l', options.limit);
|
|
3422
4904
|
addParameter('f', options.find);
|
|
4905
|
+
addParameter('p', options.projection);
|
|
3423
4906
|
addParameter('a', options.aggregate);
|
|
3424
4907
|
addParameter('o', options.order);
|
|
3425
4908
|
addParameter('s', options.skip);
|
|
4909
|
+
addParameter('c', options.concatenate);
|
|
3426
4910
|
return queryString;
|
|
3427
4911
|
};
|
|
3428
4912
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
@@ -3447,16 +4931,52 @@ var fng;
|
|
|
3447
4931
|
// changed: changed
|
|
3448
4932
|
// };
|
|
3449
4933
|
// },
|
|
3450
|
-
|
|
3451
|
-
|
|
4934
|
+
// return only the list attributes for the given record. where returnRaw is true, the record's
|
|
4935
|
+
// list attributes will be returned without transformation. otherwise, the list attributes will be concatenated
|
|
4936
|
+
// (with spaces) and returned in the form { list: string }
|
|
4937
|
+
getListAttributes: function (ref, id, returnRaw) {
|
|
4938
|
+
var actualId = typeof id === "string" ? id : id.id || id._id || id.x || id;
|
|
4939
|
+
if (typeof actualId === "object") {
|
|
4940
|
+
throw new Error("getListAttributes doesn't expect an object but was provided with ".concat(JSON.stringify(id)));
|
|
4941
|
+
}
|
|
4942
|
+
var queryString = returnRaw ? "?returnRaw=1" : "";
|
|
4943
|
+
return $http.get("/api/".concat(ref, "/").concat(actualId, "/list").concat(queryString), { cache: expCache });
|
|
3452
4944
|
},
|
|
3453
|
-
|
|
4945
|
+
// return only the list attributes for ALL records in the given collection, returning ILookupItem[]
|
|
4946
|
+
getAllListAttributes: function (ref) {
|
|
4947
|
+
return $http.get("/api/".concat(ref, "/listAll"), { cache: expCache });
|
|
4948
|
+
},
|
|
4949
|
+
// return only the list attributes for records in the given collection that satisfy the given query conditions (filter, limit etc.)
|
|
4950
|
+
// return ILookupItem[] if options.concatenate is true, else the raw documents
|
|
4951
|
+
getPagedAndFilteredList: function (ref, options) {
|
|
4952
|
+
if (options.projection) {
|
|
4953
|
+
throw new Error("Cannot use projection option for getPagedAndFilteredList, because it only returns list fields");
|
|
4954
|
+
}
|
|
4955
|
+
if (options.concatenate === undefined) {
|
|
4956
|
+
options.concatenate = false;
|
|
4957
|
+
}
|
|
4958
|
+
return $http.get("/api/".concat(ref, "/listAll").concat(generateListQuery(options)));
|
|
4959
|
+
},
|
|
4960
|
+
// return ALL attributes for records in the given collection that satisfy the given query conditions (filter, limit etc.)
|
|
4961
|
+
getPagedAndFilteredListFull: function (ref, options) {
|
|
4962
|
+
return $http.get("/api/".concat(ref).concat(generateListQuery(options)));
|
|
4963
|
+
},
|
|
4964
|
+
readRecord: function (modelName, id, formName) {
|
|
3454
4965
|
// TODO Figure out tab history updates (check for other tab-history-todos)
|
|
3455
4966
|
// let retVal;
|
|
3456
4967
|
// if (tabChangeData && tabChangeData.model === modelName && tabChangeData.id === id) {
|
|
3457
4968
|
// retVal = Promise.resolve({data:tabChangeData.record, changed: tabChangeData.changed, master: tabChangeData.master});
|
|
3458
4969
|
// } else {
|
|
3459
|
-
|
|
4970
|
+
var actualId = typeof id === "string" ? id : id.id || id._id || id.x || id;
|
|
4971
|
+
if (typeof actualId === "object") {
|
|
4972
|
+
throw new Error("readRecord doesn't expect an object but was provided with ".concat(JSON.stringify(id)));
|
|
4973
|
+
}
|
|
4974
|
+
var url = "/api/".concat(modelName, "/");
|
|
4975
|
+
if (formName) {
|
|
4976
|
+
url += "".concat(formName, "/");
|
|
4977
|
+
}
|
|
4978
|
+
url += actualId;
|
|
4979
|
+
return $http.get(url);
|
|
3460
4980
|
// retVal = $http.get('/api/' + modelName + '/' + id);
|
|
3461
4981
|
// }
|
|
3462
4982
|
// tabChangeData = null;
|
|
@@ -3464,31 +4984,36 @@ var fng;
|
|
|
3464
4984
|
},
|
|
3465
4985
|
getAll: function (modelName, _options) {
|
|
3466
4986
|
var options = angular.extend({
|
|
3467
|
-
cache:
|
|
4987
|
+
cache: useCacheForGetAll ? expCache : false
|
|
3468
4988
|
}, _options);
|
|
3469
|
-
return $http.get(
|
|
3470
|
-
},
|
|
3471
|
-
getPagedAndFilteredList: function (modelName, options) {
|
|
3472
|
-
return $http.get('/api/' + modelName + generateListQuery(options));
|
|
4989
|
+
return $http.get("/api/".concat(modelName), options);
|
|
3473
4990
|
},
|
|
3474
4991
|
deleteRecord: function (model, id) {
|
|
3475
|
-
return $http.delete(
|
|
4992
|
+
return $http.delete("/api/".concat(model, "/").concat(id));
|
|
3476
4993
|
},
|
|
3477
4994
|
updateRecord: function (modelName, id, dataToSave) {
|
|
3478
|
-
|
|
3479
|
-
return $http.post(
|
|
4995
|
+
expCache.remove("/api/".concat(modelName));
|
|
4996
|
+
return $http.post("/api/".concat(modelName, "/").concat(id), dataToSave);
|
|
3480
4997
|
},
|
|
3481
4998
|
createRecord: function (modelName, dataToSave) {
|
|
3482
|
-
|
|
3483
|
-
return $http.post(
|
|
4999
|
+
expCache.remove("/api/".concat(modelName));
|
|
5000
|
+
return $http.post("/api/".concat(modelName), dataToSave);
|
|
5001
|
+
},
|
|
5002
|
+
useCache: function (val) {
|
|
5003
|
+
useCacheForGetAll = val;
|
|
5004
|
+
},
|
|
5005
|
+
getCache: function () {
|
|
5006
|
+
return !!expCache;
|
|
5007
|
+
},
|
|
5008
|
+
clearCache: function () {
|
|
5009
|
+
expCache.removeAll();
|
|
3484
5010
|
}
|
|
3485
5011
|
};
|
|
3486
5012
|
}
|
|
3487
5013
|
services.SubmissionsService = SubmissionsService;
|
|
3488
5014
|
})(services = fng.services || (fng.services = {}));
|
|
3489
5015
|
})(fng || (fng = {}));
|
|
3490
|
-
/// <reference path="
|
|
3491
|
-
/// <reference path="../fng-types.d.ts" />
|
|
5016
|
+
/// <reference path="../../index.d.ts" />
|
|
3492
5017
|
var fng;
|
|
3493
5018
|
(function (fng) {
|
|
3494
5019
|
var controllers;
|
|
@@ -3507,6 +5032,7 @@ var fng;
|
|
|
3507
5032
|
fngInvalidRequired: 'fng-invalid-required',
|
|
3508
5033
|
allowLocationChange: true // Set when the data arrives..
|
|
3509
5034
|
};
|
|
5035
|
+
$scope.errorVisible = false;
|
|
3510
5036
|
angular.extend($scope, routingService.parsePathFunc()($location.$$path));
|
|
3511
5037
|
// Load context menu. For /person/client/:id/edit we need
|
|
3512
5038
|
// to load PersonCtrl and PersonClientCtrl
|
|
@@ -3521,17 +5047,33 @@ var fng;
|
|
|
3521
5047
|
$rootScope.$broadcast('fngFormLoadStart', $scope);
|
|
3522
5048
|
formGenerator.decorateScope($scope, formGenerator, recordHandler, $scope.sharedData);
|
|
3523
5049
|
recordHandler.decorateScope($scope, $uibModal, recordHandler, ctrlState);
|
|
3524
|
-
|
|
3525
|
-
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
$scope.sharedData.modelControllers[i].onBaseCtrlReady
|
|
5050
|
+
function processTheForm() {
|
|
5051
|
+
recordHandler.fillFormWithBackendSchema($scope, formGenerator, recordHandler, ctrlState);
|
|
5052
|
+
// Tell the 'model controllers' that they can start fiddling with baseScope
|
|
5053
|
+
for (var i = 0; i < $scope.sharedData.modelControllers.length; i++) {
|
|
5054
|
+
if ($scope.sharedData.modelControllers[i].onBaseCtrlReady) {
|
|
5055
|
+
$scope.sharedData.modelControllers[i].onBaseCtrlReady($scope);
|
|
5056
|
+
}
|
|
3529
5057
|
}
|
|
5058
|
+
$scope.$on('$destroy', function () {
|
|
5059
|
+
$scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
|
|
5060
|
+
$rootScope.$broadcast('fngControllersUnloaded');
|
|
5061
|
+
});
|
|
5062
|
+
}
|
|
5063
|
+
//Check that we are ready
|
|
5064
|
+
if (typeof fng.formsAngular.beforeProcess === "function") {
|
|
5065
|
+
fng.formsAngular.beforeProcess($scope, function (err) {
|
|
5066
|
+
if (err) {
|
|
5067
|
+
$scope.showError(err.message, 'Error preparing to process form');
|
|
5068
|
+
}
|
|
5069
|
+
else {
|
|
5070
|
+
processTheForm();
|
|
5071
|
+
}
|
|
5072
|
+
});
|
|
5073
|
+
}
|
|
5074
|
+
else {
|
|
5075
|
+
processTheForm();
|
|
3530
5076
|
}
|
|
3531
|
-
$scope.$on('$destroy', function () {
|
|
3532
|
-
$scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
|
|
3533
|
-
$rootScope.$broadcast('fngControllersUnloaded');
|
|
3534
|
-
});
|
|
3535
5077
|
}
|
|
3536
5078
|
controllers.BaseCtrl = BaseCtrl;
|
|
3537
5079
|
})(controllers = fng.controllers || (fng.controllers = {}));
|
|
@@ -3587,13 +5129,20 @@ var fng;
|
|
|
3587
5129
|
var controllers;
|
|
3588
5130
|
(function (controllers) {
|
|
3589
5131
|
/*@ngInject*/
|
|
3590
|
-
NavCtrl.$inject = ["$
|
|
3591
|
-
function NavCtrl($
|
|
5132
|
+
NavCtrl.$inject = ["$rootScope", "$window", "$scope", "$filter", "routingService", "cssFrameworkService", "securityService"];
|
|
5133
|
+
function NavCtrl($rootScope, $window, $scope, $filter, routingService, cssFrameworkService, securityService) {
|
|
3592
5134
|
function clearContextMenu() {
|
|
3593
5135
|
$scope.items = [];
|
|
3594
5136
|
$scope.contextMenu = undefined;
|
|
5137
|
+
$scope.contextMenuId = undefined;
|
|
5138
|
+
$scope.contextMenuHidden = undefined;
|
|
5139
|
+
$scope.contextMenuDisabled = undefined;
|
|
3595
5140
|
}
|
|
5141
|
+
$rootScope.navScope = $scope; // Lets plugins access menus
|
|
3596
5142
|
clearContextMenu();
|
|
5143
|
+
$scope.toggleCollapsed = function () {
|
|
5144
|
+
$scope.collapsed = !$scope.collapsed;
|
|
5145
|
+
};
|
|
3597
5146
|
/* 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 */
|
|
3598
5147
|
$scope.isCollapsed = true;
|
|
3599
5148
|
$scope.showShortcuts = false;
|
|
@@ -3621,8 +5170,8 @@ var fng;
|
|
|
3621
5170
|
}
|
|
3622
5171
|
}
|
|
3623
5172
|
function filter(event) {
|
|
3624
|
-
var tagName = (event.target
|
|
3625
|
-
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
|
|
5173
|
+
var tagName = (event.target).tagName;
|
|
5174
|
+
return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA' || tagName == "DIV" && event.target.classList.contains('ck-editor__editable'));
|
|
3626
5175
|
}
|
|
3627
5176
|
//console.log(event.keyCode, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey);
|
|
3628
5177
|
if (event.keyCode === 191 && (filter(event) || (event.ctrlKey && !event.altKey && !event.metaKey))) {
|
|
@@ -3651,7 +5200,7 @@ var fng;
|
|
|
3651
5200
|
else if (event.keyCode === 45 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
|
|
3652
5201
|
deferredBtnClick('newButton'); // Ctrl+Shift+Ins creates New record
|
|
3653
5202
|
}
|
|
3654
|
-
else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && event.altKey && !event.metaKey) {
|
|
5203
|
+
else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
|
|
3655
5204
|
deferredBtnClick('deleteButton'); // Ctrl+Shift+X deletes record
|
|
3656
5205
|
}
|
|
3657
5206
|
};
|
|
@@ -3665,8 +5214,27 @@ var fng;
|
|
|
3665
5214
|
}
|
|
3666
5215
|
return result;
|
|
3667
5216
|
};
|
|
5217
|
+
function initialiseContextMenu(menuCaption) {
|
|
5218
|
+
$scope.contextMenu = menuCaption;
|
|
5219
|
+
var menuId = "".concat(_.camelCase(menuCaption), "ContextMenu");
|
|
5220
|
+
// the context menu itself (see dropdown.ts) has an ng-if that checks for a value of
|
|
5221
|
+
// contextMenuId. let's delete this until we know we're ready to evaluate the security
|
|
5222
|
+
// of the menu items...
|
|
5223
|
+
$scope.contextMenuId = undefined;
|
|
5224
|
+
securityService.doSecurityWhenReady(function () {
|
|
5225
|
+
//... which we now are
|
|
5226
|
+
$scope.contextMenuId = menuId;
|
|
5227
|
+
$scope.contextMenuHidden = securityService.isSecurelyHidden($scope.contextMenuId);
|
|
5228
|
+
$scope.contextMenuDisabled = securityService.isSecurelyDisabled($scope.contextMenuId);
|
|
5229
|
+
});
|
|
5230
|
+
}
|
|
3668
5231
|
$scope.$on('fngControllersLoaded', function (evt, sharedData, modelName) {
|
|
3669
|
-
|
|
5232
|
+
initialiseContextMenu(sharedData.dropDownDisplay || sharedData.modelNameDisplay || $filter('titleCase')(modelName, false));
|
|
5233
|
+
if (sharedData.dropDownDisplayPromise) {
|
|
5234
|
+
sharedData.dropDownDisplayPromise.then(function (value) {
|
|
5235
|
+
initialiseContextMenu(value);
|
|
5236
|
+
});
|
|
5237
|
+
}
|
|
3670
5238
|
});
|
|
3671
5239
|
$scope.$on('fngControllersUnloaded', function (evt) {
|
|
3672
5240
|
clearContextMenu();
|
|
@@ -3682,28 +5250,60 @@ var fng;
|
|
|
3682
5250
|
}
|
|
3683
5251
|
else {
|
|
3684
5252
|
// Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
|
|
3685
|
-
var args = item.args || []
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3701
|
-
|
|
5253
|
+
var args = item.args || [];
|
|
5254
|
+
var fn = item.fn;
|
|
5255
|
+
if (typeof fn === "function") {
|
|
5256
|
+
switch (args.length) {
|
|
5257
|
+
case 0:
|
|
5258
|
+
fn();
|
|
5259
|
+
break;
|
|
5260
|
+
case 1:
|
|
5261
|
+
fn(args[0]);
|
|
5262
|
+
break;
|
|
5263
|
+
case 2:
|
|
5264
|
+
fn(args[0], args[1]);
|
|
5265
|
+
break;
|
|
5266
|
+
case 3:
|
|
5267
|
+
fn(args[0], args[1], args[2]);
|
|
5268
|
+
break;
|
|
5269
|
+
case 4:
|
|
5270
|
+
fn(args[0], args[1], args[2], args[3]);
|
|
5271
|
+
break;
|
|
5272
|
+
}
|
|
5273
|
+
}
|
|
5274
|
+
else if (fn) {
|
|
5275
|
+
throw new Error("Incorrect menu setup");
|
|
3702
5276
|
}
|
|
3703
5277
|
}
|
|
3704
5278
|
};
|
|
3705
5279
|
$scope.isHidden = function (index) {
|
|
3706
|
-
|
|
5280
|
+
function explicitlyHidden(item) {
|
|
5281
|
+
return item.isHidden ? item.isHidden() : false;
|
|
5282
|
+
}
|
|
5283
|
+
var dividerHide = false;
|
|
5284
|
+
var item = $scope.items[index];
|
|
5285
|
+
// Hide a divider if it appears under another
|
|
5286
|
+
if (item.divider) {
|
|
5287
|
+
if (index === 0) {
|
|
5288
|
+
dividerHide = true;
|
|
5289
|
+
}
|
|
5290
|
+
else {
|
|
5291
|
+
var foundVisible = false;
|
|
5292
|
+
var check = index - 1;
|
|
5293
|
+
while (check >= 0 && !dividerHide && !foundVisible) {
|
|
5294
|
+
if ($scope.items[check].divider) {
|
|
5295
|
+
dividerHide = true;
|
|
5296
|
+
}
|
|
5297
|
+
else if (!explicitlyHidden($scope.items[check])) {
|
|
5298
|
+
foundVisible = true;
|
|
5299
|
+
}
|
|
5300
|
+
else {
|
|
5301
|
+
--check;
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
}
|
|
5305
|
+
}
|
|
5306
|
+
return dividerHide || explicitlyHidden(item);
|
|
3707
5307
|
};
|
|
3708
5308
|
$scope.isDisabled = function (index) {
|
|
3709
5309
|
return $scope.items[index].isDisabled ? $scope.items[index].isDisabled() : false;
|
|
@@ -3722,6 +5322,10 @@ var fng;
|
|
|
3722
5322
|
}
|
|
3723
5323
|
return thisClass;
|
|
3724
5324
|
};
|
|
5325
|
+
var originalTitle = $window.document.title;
|
|
5326
|
+
$scope.$on('$routeChangeSuccess', function () {
|
|
5327
|
+
$window.document.title = originalTitle;
|
|
5328
|
+
});
|
|
3725
5329
|
}
|
|
3726
5330
|
controllers.NavCtrl = NavCtrl;
|
|
3727
5331
|
})(controllers = fng.controllers || (fng.controllers = {}));
|
|
@@ -3762,6 +5366,7 @@ var fng;
|
|
|
3762
5366
|
])
|
|
3763
5367
|
.controller('BaseCtrl', fng.controllers.BaseCtrl)
|
|
3764
5368
|
.controller('SaveChangesModalCtrl', fng.controllers.SaveChangesModalCtrl)
|
|
5369
|
+
.controller('LinkCtrl', fng.controllers.LinkCtrl)
|
|
3765
5370
|
.controller('ModelCtrl', fng.controllers.ModelCtrl)
|
|
3766
5371
|
.controller('NavCtrl', fng.controllers.NavCtrl)
|
|
3767
5372
|
.directive('modelControllerDropdown', fng.directives.modelControllerDropdown)
|
|
@@ -3773,6 +5378,7 @@ var fng;
|
|
|
3773
5378
|
.directive('fngNakedDate', fng.directives.fngNakedDate)
|
|
3774
5379
|
.filter('camelCase', fng.filters.camelCase)
|
|
3775
5380
|
.filter('titleCase', fng.filters.titleCase)
|
|
5381
|
+
.filter('extractTimestampFromMongoID', fng.filters.extractTimestampFromMongoID)
|
|
3776
5382
|
.service('addAllService', fng.services.addAllService)
|
|
3777
5383
|
.provider('cssFrameworkService', fng.services.cssFrameworkService)
|
|
3778
5384
|
.provider('routingService', fng.services.routingService)
|
|
@@ -3783,16 +5389,21 @@ var fng;
|
|
|
3783
5389
|
.factory('pluginHelper', fng.services.pluginHelper)
|
|
3784
5390
|
.factory('recordHandler', fng.services.recordHandler)
|
|
3785
5391
|
.factory('SchemasService', fng.services.SchemasService)
|
|
3786
|
-
.factory('SubmissionsService', fng.services.SubmissionsService)
|
|
5392
|
+
.factory('SubmissionsService', fng.services.SubmissionsService)
|
|
5393
|
+
.factory('securityService', fng.services.securityService);
|
|
3787
5394
|
})(fng || (fng = {}));
|
|
3788
5395
|
// expose the library
|
|
3789
5396
|
var formsAngular = fng.formsAngular;
|
|
3790
5397
|
|
|
3791
|
-
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>{{
|
|
3792
|
-
$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 <
|
|
3793
|
-
$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
|
|
5398
|
+
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');
|
|
5399
|
+
$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');
|
|
5400
|
+
$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');
|
|
5401
|
+
$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');
|
|
5402
|
+
$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');
|
|
5403
|
+
$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');
|
|
5404
|
+
$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');
|
|
3794
5405
|
$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');
|
|
3795
|
-
$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');
|
|
3796
|
-
$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');
|
|
3797
|
-
$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 <
|
|
3798
|
-
$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 <
|
|
5406
|
+
$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');
|
|
5407
|
+
$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');
|
|
5408
|
+
$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');
|
|
5409
|
+
$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');}]);
|