forms-angular 0.12.0-beta.18 → 0.12.0-beta.182

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.
@@ -9,12 +9,12 @@ var fng;
9
9
  restrict: 'AE',
10
10
  replace: true,
11
11
  template: '<li ng-show="items.length > 0" class="mcdd" uib-dropdown>' +
12
- ' <a uib-dropdown-toggle>' +
12
+ ' <a href="#" uib-dropdown-toggle>' +
13
13
  ' {{contextMenu}} <b class="caret"></b>' +
14
14
  ' </a>' +
15
15
  ' <ul class="uib-dropdown-menu dropdown-menu">' +
16
16
  ' <li ng-repeat="choice in items" ng-hide="isHidden($index)" ng-class="dropdownClass($index)">' +
17
- ' <a ng-show="choice.text" class="dropdown-option" ng-href="{{choice.url}}" ng-click="doClick($index, $event)">' +
17
+ ' <a ng-show="choice.text" class="dropdown-option" ng-href="{{choice.url || choice.urlFunc()}}" ng-click="doClick($index, $event)">' +
18
18
  ' {{choice.text}}' +
19
19
  ' </a>' +
20
20
  ' </li>' +
@@ -31,16 +31,11 @@ var fng;
31
31
  var directives;
32
32
  (function (directives) {
33
33
  /*@ngInject*/
34
- function errorDisplay() {
34
+ errorDisplay.$inject = ["cssFrameworkService"];
35
+ function errorDisplay(cssFrameworkService) {
35
36
  return {
36
37
  restrict: 'E',
37
- template: '<div id="display-error" ng-show="errorMessage" ng-class="css(\'rowFluid\')">' +
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>'
38
+ templateUrl: 'error-display-' + cssFrameworkService.framework() + '.html'
44
39
  };
45
40
  }
46
41
  directives.errorDisplay = errorDisplay;
@@ -48,6 +43,44 @@ var fng;
48
43
  })(fng || (fng = {}));
49
44
  /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
50
45
  var fng;
46
+ (function (fng) {
47
+ var controllers;
48
+ (function (controllers) {
49
+ /*@ngInject*/
50
+ LinkCtrl.$inject = ["$scope"];
51
+ function LinkCtrl($scope) {
52
+ /**
53
+ * In the event that the link is part of a form that belongs to a (ui-bootstrap) modal,
54
+ * close the modal
55
+ */
56
+ $scope.checkNotModal = function () {
57
+ var elm = $scope.element[0];
58
+ var parentNode;
59
+ var finished = false;
60
+ var fakeEvt = {
61
+ preventDefault: angular.noop,
62
+ stopPropagation: angular.noop,
63
+ target: 1,
64
+ currentTarget: 1
65
+ };
66
+ do {
67
+ parentNode = elm.parentNode;
68
+ if (!parentNode) {
69
+ finished = true;
70
+ }
71
+ else if (typeof parentNode.getAttribute === "function" && parentNode.getAttribute('uib-modal-window')) {
72
+ angular.element(elm).scope().close(fakeEvt);
73
+ finished = true;
74
+ }
75
+ else {
76
+ elm = parentNode;
77
+ }
78
+ } while (!finished);
79
+ };
80
+ }
81
+ controllers.LinkCtrl = LinkCtrl;
82
+ })(controllers = fng.controllers || (fng.controllers = {}));
83
+ })(fng || (fng = {}));
51
84
  (function (fng) {
52
85
  var directives;
53
86
  (function (directives) {
@@ -58,38 +91,124 @@ var fng;
58
91
  restrict: 'E',
59
92
  scope: { dataSrc: '&model' },
60
93
  link: function (scope, element, attrs) {
61
- var ref = JSON.parse(attrs['ref']);
94
+ var ref = attrs['ref'];
95
+ var isLabel = attrs['text'] && (unescape(attrs['text']) !== attrs['text']);
62
96
  var form = attrs['form'];
97
+ var linktab = attrs['linktab'];
63
98
  scope['readonly'] = attrs['readonly'];
99
+ scope['element'] = element;
64
100
  form = form ? form + '/' : '';
65
- if (attrs['text'] && attrs['text'].length > 0) {
66
- scope['text'] = attrs['text'];
67
- }
68
- var index = scope['$parent']['$index'];
69
- scope.$watch('dataSrc()', function (newVal) {
70
- if (newVal) {
71
- if (typeof index !== 'undefined' && angular.isArray(newVal)) {
72
- newVal = newVal[index];
73
- }
74
- scope['link'] = routingService.buildUrl(ref.collection + '/' + form + newVal + '/edit');
75
- if (!scope['text']) {
76
- SubmissionsService.getListAttributes(ref, newVal).then(function (response) {
77
- var data = response.data;
78
- if (data.success === false) {
79
- scope['text'] = data.err;
101
+ linktab = linktab ? '/' + linktab : '';
102
+ if (isLabel) {
103
+ var workScope = scope;
104
+ var workString = '';
105
+ while (typeof workScope['baseSchema'] !== "function" && workScope.$parent) {
106
+ if (typeof workScope['$index'] !== "undefined") {
107
+ throw new Error('No support for $index at this level - ' + workString);
108
+ }
109
+ workScope = workScope.$parent;
110
+ workString = workString + '$parent.';
111
+ }
112
+ var attrib = attrs['fld'];
113
+ var watchExpression;
114
+ var splitAttrib = attrib.split('.');
115
+ if (scope.$parent.subDoc && (scope.$parent.subDoc[attrib] || scope.$parent.subDoc[splitAttrib[splitAttrib.length - 1]])) {
116
+ // Support for use in directives in arrays
117
+ if (scope.$parent.subDoc[attrib]) {
118
+ watchExpression = workString + 'subDoc.' + attrib;
119
+ }
120
+ else {
121
+ watchExpression = workString + 'subDoc.' + splitAttrib[splitAttrib.length - 1];
122
+ }
123
+ }
124
+ else {
125
+ if (typeof workScope['$index'] !== "undefined") {
126
+ attrib = splitAttrib.pop();
127
+ attrib = splitAttrib.join('.') + '[' + workScope['$index'] + '].' + attrib;
128
+ }
129
+ else {
130
+ attrib = '.' + attrib;
131
+ }
132
+ watchExpression = workString + 'record' + attrib;
133
+ }
134
+ scope.$watch(watchExpression, function (newVal) {
135
+ if (newVal) {
136
+ if (/^[a-f0-9]{24}/.test(newVal.toString())) {
137
+ newVal = newVal.slice(0, 24);
138
+ }
139
+ else if (newVal.id && /^[a-f0-9]{24}/.test(newVal.id)) {
140
+ newVal = newVal.id.slice(0, 24);
141
+ }
142
+ else if (scope.$parent["f_" + attrs['fld'] + "Options"]) {
143
+ // extract from lookups
144
+ var i = scope.$parent["f_" + attrs['fld'] + "Options"].indexOf(newVal);
145
+ if (i > -1) {
146
+ newVal = scope.$parent["f_" + attrs['fld'] + "_ids"][i];
80
147
  }
81
148
  else {
82
- scope['text'] = data.list;
149
+ newVal = undefined;
83
150
  }
84
- }, function (response) {
85
- scope['text'] = 'Error ' + response.status + ': ' + response.data;
86
- });
151
+ }
152
+ else {
153
+ newVal = undefined;
154
+ }
155
+ if (newVal) {
156
+ scope['link'] = routingService.buildUrl(ref + '/' + form + (newVal.id || newVal) + '/edit' + linktab);
157
+ }
158
+ else {
159
+ scope['link'] = undefined;
160
+ }
87
161
  }
88
- }
89
- }, true);
162
+ else {
163
+ scope['link'] = undefined;
164
+ }
165
+ }, true);
166
+ }
167
+ else {
168
+ if (attrs['text'] && attrs['text'].length > 0) {
169
+ scope['text'] = attrs['text'];
170
+ }
171
+ var index = scope['$parent']['$index'];
172
+ scope.$watch('dataSrc()', function (newVal) {
173
+ if (newVal) {
174
+ if (typeof index !== 'undefined' && angular.isArray(newVal)) {
175
+ newVal = newVal[index];
176
+ }
177
+ scope['link'] = routingService.buildUrl(ref + '/' + form + newVal + '/edit' + linktab);
178
+ if (!scope['text']) {
179
+ SubmissionsService.getListAttributes(ref, newVal).then(function (response) {
180
+ var data = response.data;
181
+ if (data.success === false) {
182
+ scope['text'] = data.err;
183
+ }
184
+ else {
185
+ scope['text'] = data.list;
186
+ }
187
+ }, function (response) {
188
+ scope['text'] = 'Error ' + response.status + ': ' + response.data;
189
+ });
190
+ }
191
+ }
192
+ }, true);
193
+ }
90
194
  },
195
+ controller: "LinkCtrl",
91
196
  template: function (element, attrs) {
92
- return attrs.readonly ? '<span class="fng-link">{{text}}</span>' : '<a href="{{ link }}" class="fng-link">{{text}}</a>';
197
+ function handleAnchor(contents) {
198
+ return "<a ng-click=\"checkNotModal()\" ng-href=\"{{ link || '#' }}\" class=\"fng-link\">".concat(contents, "</a>");
199
+ }
200
+ var retVal;
201
+ if (attrs.readonly) {
202
+ retVal = '<span class="fng-link">{{text}}</span>';
203
+ }
204
+ else if (attrs['text'] && unescape(attrs['text']) !== attrs['text']) {
205
+ retVal = handleAnchor(unescape(attrs['text']));
206
+ // retVal = '<a href="{{ link }}" class="fng-link">{{text}}</a>';
207
+ }
208
+ else {
209
+ retVal = handleAnchor('{{text}}');
210
+ }
211
+ return retVal;
93
212
  }
94
213
  };
95
214
  }
@@ -146,7 +265,7 @@ var fng;
146
265
  // <input type="email" class="form-control" id="exampleInputEmail1" placeholder="Enter email">
147
266
  // </div>
148
267
  //
149
- // Inline
268
+ // Inline or stacked
150
269
  // <div class="form-group">
151
270
  // <label class="sr-only" for="exampleInputEmail2">Email address</label>
152
271
  // <input type="email" class="form-control" id="exampleInputEmail2" placeholder="Enter email">
@@ -165,7 +284,7 @@ var fng;
165
284
  // <input type="text" placeholder="Type something…">
166
285
  // <span class="help-block">Example block-level help text here.</span>
167
286
  //
168
- // Inline
287
+ // Inline or Stacked
169
288
  // <input type="text" class="input-small" placeholder="Email">
170
289
  var subkeys = [];
171
290
  var tabsSetup = tabsSetupState.N;
@@ -206,7 +325,7 @@ var fng;
206
325
  var lastPart = compoundName.slice(root.length + 1);
207
326
  if (options.index) {
208
327
  modelString += root + '[' + options.index + '].' + lastPart;
209
- idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[|\]\.)/g, '-');
328
+ idString = 'f_' + modelString.slice(modelBase.length).replace(/(\.|\[|]\.)/g, '-');
210
329
  }
211
330
  else {
212
331
  modelString += root;
@@ -228,18 +347,33 @@ var fng;
228
347
  var allInputsVars = formMarkupHelper.allInputsVars(scope, fieldInfo, options, modelString, idString, nameString);
229
348
  var common = allInputsVars.common;
230
349
  var value;
231
- var requiredStr = (isRequired || fieldInfo.required) ? ' required' : '';
232
350
  isRequired = isRequired || fieldInfo.required;
233
- var requiredStr = isRequired ? ' required' : '';
351
+ var requiredStr = isRequired ? ' required ' : '';
234
352
  var enumInstruction;
353
+ function handleReadOnlyDisabled(readonly) {
354
+ var retVal = '';
355
+ if (readonly) {
356
+ // despite the option being "readonly", we should use disabled and ng-disabled rather than their readonly
357
+ // equivalents (which give controls the appearance of being read-only, but don't actually prevent user
358
+ // interaction)
359
+ if (typeof readonly === "boolean") {
360
+ retVal = " disabled ";
361
+ }
362
+ else {
363
+ retVal = " ng-disabled=\"".concat(readonly, "\" ");
364
+ }
365
+ }
366
+ return retVal;
367
+ }
235
368
  switch (fieldInfo.type) {
236
369
  case 'select':
237
370
  if (fieldInfo.select2) {
238
371
  value = '<input placeholder="fng-select2 has been removed" readonly>';
239
372
  }
240
373
  else {
241
- common += (fieldInfo.readonly ? 'disabled ' : '');
374
+ common += handleReadOnlyDisabled(fieldInfo.readonly);
242
375
  common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
376
+ common += " aria-label=\"".concat(fieldInfo.label && fieldInfo.label !== "" ? fieldInfo.label : fieldInfo.name, "\" ");
243
377
  value = '<select ' + common + 'class="' + allInputsVars.formControl.trim() + allInputsVars.compactClass + allInputsVars.sizeClassBS2 + '" ' + requiredStr + '>';
244
378
  if (!isRequired) {
245
379
  value += '<option></option>';
@@ -268,25 +402,31 @@ var fng;
268
402
  }
269
403
  break;
270
404
  case 'link':
271
- if (typeof fieldInfo.ref === 'string') {
272
- fieldInfo.ref = { type: 'lookup', collection: fieldInfo.ref };
273
- console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(fieldInfo.ref));
274
- }
275
- value = '<fng-link model="' + modelString + '" ref="' + JSON.stringify(fieldInfo.ref).replace(/"/g, '&quot;') + '"';
405
+ value = '<fng-link model="' + modelString + '" ref="' + fieldInfo.ref + '"';
276
406
  if (fieldInfo.form) {
277
407
  value += ' form="' + fieldInfo.form + '"';
278
408
  }
279
- if (fieldInfo.linkText) {
280
- value += ' text="' + fieldInfo.linkText + '"';
409
+ if (fieldInfo.linktab) {
410
+ value += ' linktab="' + fieldInfo.linktab + '"';
411
+ }
412
+ if (fieldInfo.linktext) {
413
+ value += ' text="' + fieldInfo.linktext + '"';
281
414
  }
282
415
  if (fieldInfo.readonly) {
283
- value += ' readonly="true"';
416
+ if (typeof fieldInfo.readonly === "boolean") {
417
+ value += " readonly=\"true\"";
418
+ }
419
+ else {
420
+ value += " ng-readonly=\"".concat(fieldInfo.readonly, "\"");
421
+ }
284
422
  }
285
423
  value += '></fng-link>';
286
424
  break;
287
425
  case 'radio':
288
426
  value = '';
289
- common += requiredStr + (fieldInfo.readonly ? ' disabled ' : ' ');
427
+ common += requiredStr;
428
+ common += handleReadOnlyDisabled(fieldInfo.readonly);
429
+ common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
290
430
  var separateLines = options.formstyle === 'vertical' || (options.formstyle !== 'inline' && !fieldInfo.inlineRadio);
291
431
  if (angular.isArray(fieldInfo.options)) {
292
432
  if (options.subschema) {
@@ -296,8 +436,7 @@ var fng;
296
436
  var thisCommon_1;
297
437
  angular.forEach(fieldInfo.options, function (optValue, idx) {
298
438
  thisCommon_1 = common.replace('id="', 'id="' + idx + '-');
299
- value += '<input ' + thisCommon_1 + 'type="radio"';
300
- value += ' value="' + optValue + '">' + optValue;
439
+ value += "<input ".concat(thisCommon_1, " type=\"radio\" aria-label=\"").concat(optValue, "\" value=\"").concat(optValue, "\">").concat(optValue);
301
440
  if (separateLines) {
302
441
  value += '<br />';
303
442
  }
@@ -312,18 +451,16 @@ var fng;
312
451
  }
313
452
  enumInstruction = generateEnumInstructions();
314
453
  value += '<' + tagType + ' ng-repeat="option in ' + enumInstruction.repeat + '">';
315
- value += '<input ' + common.replace('id="', 'id="{{$index}}-') + ' type="radio" value="{{' + enumInstruction.value + '}}"> {{';
316
- value += enumInstruction.label || enumInstruction.value;
317
- value += ' }} </' + tagType + '> ';
454
+ value += "<input ".concat(common.replace('id="', 'id="{{$index}}-'), " type=\"radio\" aria-label=\"").concat(enumInstruction.value, "\" value=\"{{ ").concat(enumInstruction.value, " }}\"> {{ ").concat(enumInstruction.label || enumInstruction.value, " }} </").concat(tagType, "> ");
318
455
  }
319
456
  break;
320
457
  case 'checkbox':
321
- common += requiredStr + (fieldInfo.readonly ? ' disabled ' : ' ');
458
+ common += requiredStr;
459
+ common += handleReadOnlyDisabled(fieldInfo.readonly);
460
+ common += fieldInfo.add ? (' ' + fieldInfo.add + ' ') : '';
461
+ value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
322
462
  if (cssFrameworkService.framework() === 'bs3') {
323
- value = '<div class="checkbox"><input ' + common + 'type="checkbox"></div>';
324
- }
325
- else {
326
- value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
463
+ value = '<div class="checkbox">' + value + '</div>';
327
464
  }
328
465
  break;
329
466
  default:
@@ -338,12 +475,13 @@ var fng;
338
475
  }
339
476
  }
340
477
  if (fieldInfo.editor === 'ckEditor') {
478
+ console.log('Deprecation Warning: "editor" property deprecated - use "add"');
341
479
  common += 'ckeditor = "" ';
342
480
  if (cssFrameworkService.framework() === 'bs3') {
343
481
  allInputsVars.sizeClassBS3 = 'col-xs-12';
344
482
  }
345
483
  }
346
- value = '<textarea ' + common + ' />';
484
+ value = '<textarea ' + common + '></textarea>';
347
485
  }
348
486
  else {
349
487
  value = formMarkupHelper.generateSimpleInput(common, fieldInfo, options);
@@ -363,6 +501,9 @@ var fng;
363
501
  case 'inline':
364
502
  result = 'form-inline';
365
503
  break;
504
+ case 'stacked':
505
+ result = 'form-stacked';
506
+ break;
366
507
  case 'horizontalCompact':
367
508
  result = 'form-horizontal compact';
368
509
  break;
@@ -390,7 +531,7 @@ var fng;
390
531
  if (tabNo >= 0) {
391
532
  // TODO Figure out tab history updates (check for other tab-history-todos)
392
533
  // result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"'
393
- result.before = '<uib-tab select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"';
534
+ result.before = '<uib-tab deselect="tabDeselect($event, $selectedIndex)" select="updateQueryForTab(\'' + info.title + '\')" heading="' + info.title + '"';
394
535
  if (tabNo > 0) {
395
536
  result.before += 'active="tabs[' + tabNo + '].active"';
396
537
  }
@@ -451,6 +592,41 @@ var fng;
451
592
  }
452
593
  return result;
453
594
  };
595
+ var generateInlineHeaders = function (instructionsArray, options, model, evenWhenEmpty) {
596
+ // "column" headers for nested schemas that use formStyle: "inline" will only line up with their respective
597
+ // controls when widths are applied to both the cg_f_xxxx and col_label_xxxx element using css.
598
+ // Likely, the widths will need to be the same, so consider using the following:
599
+ // div[id$="_f_<collection>_<field>"] {
600
+ // width: 100px;
601
+ // }
602
+ // one column can grow to the remaining available width thus:
603
+ // div[id$="_f_<collection>_<field>"] {
604
+ // flex-grow: 1;
605
+ // }
606
+ var hideWhenEmpty = evenWhenEmpty ? "" : "ng-hide=\"!".concat(model, " || ").concat(model, ".length === 0\"");
607
+ var res = "<div class=\"inline-col-headers\" style=\"display:flex\" ".concat(hideWhenEmpty, ">");
608
+ for (var _i = 0, instructionsArray_1 = instructionsArray; _i < instructionsArray_1.length; _i++) {
609
+ var info = instructionsArray_1[_i];
610
+ // need to call this now to ensure the id is set. will probably be (harmlessly) called again later.
611
+ inferMissingProperties(info, options);
612
+ res += '<div ';
613
+ info.showWhen = info.showWhen || info.showwhen; // deal with use within a directive
614
+ if (info.showWhen) {
615
+ if (typeof info.showWhen === 'string') {
616
+ res += 'ng-show="' + info.showWhen + '"';
617
+ }
618
+ else {
619
+ res += 'ng-show="' + formMarkupHelper.generateNgShow(info.showWhen, model) + '"';
620
+ }
621
+ }
622
+ if (info.id && typeof info.id.replace === "function") {
623
+ res += ' id="col_label_' + info.id.replace(/\./g, '-') + '"';
624
+ }
625
+ res += " class=\"inline-col-header\"><label for=\"".concat(info.id, "\" class=\"control-label\">").concat(info.label, "</label></div>");
626
+ }
627
+ res += "</div>";
628
+ return res;
629
+ };
454
630
  var handleField = function (info, options) {
455
631
  var fieldChrome = formMarkupHelper.fieldChrome(scope, info, options);
456
632
  var template = fieldChrome.template;
@@ -473,7 +649,8 @@ var fng;
473
649
  formstyle: options.formstyle,
474
650
  subkey: schemaDefName + '_subkey',
475
651
  subkeyno: arraySel,
476
- subschemaroot: info.name
652
+ subschemaroot: info.name,
653
+ suppressNestingWarning: info.suppressNestingWarning
477
654
  });
478
655
  template += topAndTail.after;
479
656
  }
@@ -481,7 +658,9 @@ var fng;
481
658
  }
482
659
  else {
483
660
  if (options.subschema) {
484
- console.log('Attempts at supporting deep nesting have been removed - will hopefully be re-introduced at a later date');
661
+ if (!options.suppressNestingWarning) {
662
+ console.log('Attempts at supporting deep nesting have been removed - will hopefully be re-introduced at a later date');
663
+ }
485
664
  }
486
665
  else {
487
666
  var model = (options.model || 'record') + '.' + info.name;
@@ -503,20 +682,24 @@ var fng;
503
682
  }
504
683
  }
505
684
  /* Array body */
506
- template += '<ol class="sub-doc"' + (info.sortable ? " ui-sortable=\"sortableOptions\" ng-model=\"" + model + "\"" : '') + '>';
685
+ if (info.formStyle === "inline" && info.inlineHeaders) {
686
+ template += generateInlineHeaders(info.schema, options, model, info.inlineHeaders === "always");
687
+ }
688
+ template += '<ol class="sub-doc"' + (info.sortable ? " ui-sortable=\"sortableOptions\" ng-model=\"".concat(model, "\"") : '') + '>';
507
689
  template += '<li ng-form class="' + (cssFrameworkService.framework() === 'bs2' ? 'row-fluid ' : '') +
508
- convertFormStyleToClass(info.formStyle) + '" name="form_' + niceName + '{{$index}}" class="sub-doc well" id="' + info.id + 'List_{{$index}}" ' +
690
+ (info.inlineHeaders ? 'width-controlled ' : '') +
691
+ convertFormStyleToClass(info.formStyle) + ' ' + (info.ngClass ? "ng-class:" + info.ngClass : "") + '" name="form_' + niceName + '{{$index}}" class="sub-doc well" id="' + info.id + 'List_{{$index}}" ' +
509
692
  ' ng-repeat="subDoc in ' + model + ' track by $index">';
510
693
  if (cssFrameworkService.framework() === 'bs2') {
511
694
  template += '<div class="row-fluid sub-doc">';
512
695
  }
513
- if (!info.noRemove || info.customSubDoc) {
696
+ if (info.noRemove !== true || info.customSubDoc) {
514
697
  template += ' <div class="sub-doc-btns">';
515
698
  if (typeof info.customSubDoc == 'string') {
516
699
  template += info.customSubDoc;
517
700
  }
518
701
  if (info.noRemove !== true) {
519
- template += "<button " + (info.noRemove ? 'ng-hide="' + info.noRemove + '"' : '') + " name=\"remove_" + info.id + "_btn\" ng-click=\"remove('" + info.name + "', $index, $event)\"";
702
+ template += "<button ".concat(info.noRemove ? 'ng-hide="' + info.noRemove + '"' : '', " name=\"remove_").concat(info.id, "_btn\" ng-click=\"remove('").concat(info.name, "', $index, $event)\"");
520
703
  if (info.remove) {
521
704
  template += ' class="remove-btn btn btn-mini btn-default btn-xs form-btn"><i class="' + formMarkupHelper.glyphClass() + '-minus"></i> Remove';
522
705
  }
@@ -533,31 +716,64 @@ var fng;
533
716
  }
534
717
  template += '</div> ';
535
718
  }
719
+ var parts = void 0;
720
+ if (info.subDocContainerType) {
721
+ var containerType = scope[info.subDocContainerType] || info.subDocContainerType;
722
+ var containerProps = Object.assign({ containerType: containerType }, info.subDocContainerProps);
723
+ parts = containerInstructions(containerProps);
724
+ }
725
+ if (parts === null || parts === void 0 ? void 0 : parts.before) {
726
+ template += parts.before;
727
+ }
536
728
  template += processInstructions(info.schema, false, {
537
729
  subschema: 'true',
538
730
  formstyle: info.formStyle,
539
731
  model: options.model,
540
- subschemaroot: info.name
732
+ subschemaroot: info.name,
733
+ suppressNestingWarning: info.suppressNestingWarning
541
734
  });
735
+ if (parts === null || parts === void 0 ? void 0 : parts.after) {
736
+ template += parts.after;
737
+ }
542
738
  if (cssFrameworkService.framework() === 'bs2') {
543
739
  template += ' </div>';
544
740
  }
545
741
  template += '</li>';
546
742
  template += '</ol>';
547
743
  /* Array footer */
548
- if (info.noAdd !== true || typeof info.customFooter == 'string') {
744
+ if (info.noAdd !== true || typeof info.customFooter == 'string' || info.noneIndicator) {
549
745
  var footer = '';
550
746
  if (typeof info.customFooter == 'string') {
551
747
  footer = info.customFooter;
552
748
  }
553
- if (info.noAdd !== true) {
554
- footer += "<button " + (info.noAdd ? 'ng-hide="' + info.noAdd + '"' : '') + " id=\"add_" + info.id + "_btn\" class=\"add-btn btn btn-default btn-xs btn-mini\" ng-click=\"add('" + info.name + "',$event)\">\n <i class=\"' + formMarkupHelper.glyphClass() + '-plus\"></i> \n Add\n </button>";
555
- }
556
- if (cssFrameworkService.framework() === 'bs3') {
557
- template += '<div class="row schema-foot"><div class="col-sm-offset-3">' + footer + '</div></div>';
749
+ var hideCond = '';
750
+ var indicatorShowCond = "".concat(model, ".length == 0");
751
+ if (info.noAdd === true) {
752
+ indicatorShowCond = "ng-show=\"".concat(indicatorShowCond, "\"");
558
753
  }
559
754
  else {
560
- template += '<div class = "schema-foot ">' + footer + '</div>';
755
+ hideCond = info.noAdd ? "ng-hide=\"".concat(info.noAdd, "\"") : '';
756
+ indicatorShowCond = info.noAdd ? "ng-show=\"".concat(info.noAdd, " && ").concat(indicatorShowCond, "\"") : '';
757
+ footer += "<button ".concat(hideCond, " id=\"add_").concat(info.id, "_btn\" class=\"add-btn btn btn-default btn-xs btn-mini\" ng-click=\"add('").concat(info.name, "',$event)\">\n <i class=\"' + formMarkupHelper.glyphClass() + '-plus\"></i> \n Add\n </button>");
758
+ }
759
+ if (info.noneIndicator) {
760
+ footer += "<span ".concat(indicatorShowCond, " class=\"none_indicator\" id=\"no_").concat(info.id, "_indicator\">None</span>");
761
+ // hideCond for the schema-foot is if there's no add button and no indicator
762
+ hideCond = "".concat(model, ".length > 0");
763
+ if (info.noAdd === true) {
764
+ hideCond = "ng-hide=\"".concat(hideCond, "\"");
765
+ }
766
+ else {
767
+ hideCond = info.noAdd ? "ng-hide=\"".concat(info.noAdd, " && ").concat(hideCond, "\"") : '';
768
+ }
769
+ }
770
+ if (footer !== '') {
771
+ if (cssFrameworkService.framework() === 'bs3') {
772
+ template += "<div ".concat(hideCond, " class=\"row schema-foot\"><div class=\"col-sm-offset-3\">").concat(footer, "</div></div>");
773
+ }
774
+ else {
775
+ template += "<div ".concat(hideCond, " class = \"schema-foot \">").concat(footer, "</div>");
776
+ }
561
777
  }
562
778
  }
563
779
  }
@@ -569,8 +785,8 @@ var fng;
569
785
  var controlDivClasses = formMarkupHelper.controlDivClasses(options);
570
786
  if (info.array) {
571
787
  controlDivClasses.push('fng-array');
572
- if (options.formstyle === 'inline') {
573
- throw new Error('Cannot use arrays in an inline form');
788
+ if (options.formstyle === 'inline' || options.formstyle === 'stacked') {
789
+ throw new Error('Cannot use arrays in an inline or stacked form');
574
790
  }
575
791
  template += formMarkupHelper.label(scope, info, info.type !== 'link', options);
576
792
  template += formMarkupHelper.handleArrayInputAndControlDiv(generateInput(info, info.type === 'link' ? null : 'arrayItem.x', true, info.id + '_{{$index}}', options), controlDivClasses, info, options);
@@ -578,25 +794,27 @@ var fng;
578
794
  else {
579
795
  // Single fields here
580
796
  template += formMarkupHelper.label(scope, info, null, options);
581
- if (options.required) {
582
- console.log("********* Options required - found it ********");
583
- }
584
797
  template += formMarkupHelper.handleInputAndControlDiv(generateInput(info, null, options.required, info.id, options), controlDivClasses);
585
798
  }
586
799
  }
587
800
  template += fieldChrome.closeTag;
588
801
  return template;
589
802
  };
590
- var inferMissingProperties = function (info) {
803
+ var inferMissingProperties = function (info, options) {
591
804
  // infer missing values
592
805
  info.type = info.type || 'text';
593
806
  if (info.id) {
594
- if (typeof info.id === 'number' || (info.id[0] >= 0 && info.id <= '9')) {
807
+ if (typeof info.id === 'number' || info.id.match(/^[0-9]/)) {
595
808
  info.id = '_' + info.id;
596
809
  }
597
810
  }
598
811
  else {
599
- info.id = 'f_' + info.name.replace(/\./g, '_');
812
+ if (options && options.noid) {
813
+ info.id = null;
814
+ }
815
+ else {
816
+ info.id = 'f_' + info.name.replace(/\./g, '_');
817
+ }
600
818
  }
601
819
  info.label = (info.label !== undefined) ? (info.label === null ? '' : info.label) : $filter('titleCase')(info.name.split('.').slice(-1)[0]);
602
820
  };
@@ -607,7 +825,11 @@ var fng;
607
825
  if (instructionsArray) {
608
826
  for (var anInstruction = 0; anInstruction < instructionsArray.length; anInstruction++) {
609
827
  var info = instructionsArray[anInstruction];
610
- if (anInstruction === 0 && topLevel && !options.schema.match(/$_schema_/) && typeof info.add !== 'object') {
828
+ if (options.viewform) {
829
+ info = angular.copy(info);
830
+ info.readonly = true;
831
+ }
832
+ if (anInstruction === 0 && topLevel && !options.schema.match(/\$_schema_/) && typeof info.add !== 'object') {
611
833
  info.add = info.add ? ' ' + info.add + ' ' : '';
612
834
  if (info.add.indexOf('ui-date') === -1 && !options.noautofocus && !info.containerType) {
613
835
  info.add = info.add + 'autofocus ';
@@ -616,9 +838,10 @@ var fng;
616
838
  var callHandleField = true;
617
839
  if (info.directive) {
618
840
  var directiveName = info.directive;
619
- var newElement = '<' + directiveName + ' model="' + (options.model || 'record') + '"';
841
+ var newElement = info.customHeader || "";
842
+ newElement += '<' + directiveName + ' model="' + (options.model || 'record') + '"';
620
843
  var thisElement = element[0];
621
- inferMissingProperties(info);
844
+ inferMissingProperties(info, options);
622
845
  for (var i = 0; i < thisElement.attributes.length; i++) {
623
846
  var thisAttr = thisElement.attributes[i];
624
847
  switch (thisAttr.nodeName) {
@@ -654,7 +877,9 @@ var fng;
654
877
  break;
655
878
  case 'object':
656
879
  for (var subAdd in info.add) {
657
- newElement += ' ' + subAdd + '="' + info.add[subAdd].toString().replace(/"/g, '&quot;') + '"';
880
+ if (info.add.hasOwnProperty(subAdd)) {
881
+ newElement += ' ' + subAdd + '="' + info.add[subAdd].toString().replace(/"/g, '&quot;') + '"';
882
+ }
658
883
  }
659
884
  break;
660
885
  default:
@@ -663,13 +888,21 @@ var fng;
663
888
  break;
664
889
  case directiveCamel:
665
890
  for (var subProp in info[prop]) {
666
- newElement += info.directive + '-' + subProp + '="' + info[prop][subProp] + '"';
891
+ if (info[prop].hasOwnProperty(subProp)) {
892
+ newElement += " ".concat(info.directive, "-").concat(subProp, "=\"");
893
+ if (typeof info[prop][subProp] === 'string') {
894
+ newElement += "".concat(info[prop][subProp].replace(/"/g, '&quot;'), "\"");
895
+ }
896
+ else {
897
+ newElement += "".concat(JSON.stringify(info[prop][subProp]).replace(/"/g, '&quot;'), "\"");
898
+ }
899
+ }
667
900
  }
668
901
  break;
669
902
  default:
670
903
  if (info[prop]) {
671
904
  if (typeof info[prop] === 'string') {
672
- newElement += ' fng-fld-' + prop + '="' + info[prop].toString().replace(/"/g, '&quot;') + '"';
905
+ newElement += ' fng-fld-' + prop + '="' + info[prop].replace(/"/g, '&quot;') + '"';
673
906
  }
674
907
  else {
675
908
  newElement += ' fng-fld-' + prop + '="' + JSON.stringify(info[prop]).replace(/"/g, '&quot;') + '"';
@@ -685,6 +918,7 @@ var fng;
685
918
  }
686
919
  }
687
920
  newElement += 'ng-model="' + info.name + '"></' + directiveName + '>';
921
+ newElement += (info.customFooter || "");
688
922
  result += newElement;
689
923
  callHandleField = false;
690
924
  }
@@ -721,9 +955,7 @@ var fng;
721
955
  else if (options.subkey) {
722
956
  // 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)
723
957
  var objectToSearch = angular.isArray(scope[options.subkey]) ? scope[options.subkey][0].keyList : scope[options.subkey].keyList;
724
- if (_.find(objectToSearch, function (value, key) {
725
- return scope[options.subkey].path + '.' + key === info.name;
726
- })) {
958
+ if (_.find(objectToSearch, function (value, key) { return scope[options.subkey].path + '.' + key === info.name; })) {
727
959
  callHandleField = false;
728
960
  }
729
961
  }
@@ -731,7 +963,7 @@ var fng;
731
963
  // if (groupId) {
732
964
  // scope['showHide' + groupId] = true;
733
965
  // }
734
- inferMissingProperties(info);
966
+ inferMissingProperties(info, options);
735
967
  result += handleField(info, options);
736
968
  }
737
969
  }
@@ -745,8 +977,9 @@ var fng;
745
977
  var unwatch = scope.$watch(attrs.schema, function (newValue) {
746
978
  if (newValue) {
747
979
  var newArrayValue = angular.isArray(newValue) ? newValue : [newValue]; // otherwise some old tests stop working for no real reason
748
- if (newArrayValue.length > 0) {
980
+ if (newArrayValue.length > 0 && typeof unwatch === "function") {
749
981
  unwatch();
982
+ unwatch = null;
750
983
  var elementHtml = '';
751
984
  var recordAttribute = attrs.model || 'record'; // By default data comes from scope.record
752
985
  var theRecord = scope[recordAttribute];
@@ -760,12 +993,13 @@ var fng;
760
993
  var customAttrs = '';
761
994
  for (var thisAttr in attrs) {
762
995
  if (attrs.hasOwnProperty(thisAttr)) {
763
- if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model'].indexOf(thisAttr) === -1) {
996
+ if (thisAttr[0] !== '$' && ['name', 'formstyle', 'schema', 'subschema', 'model', 'viewform'].indexOf(thisAttr) === -1) {
764
997
  customAttrs += ' ' + attrs.$attr[thisAttr] + '="' + attrs[thisAttr] + '"';
765
998
  }
766
999
  }
767
1000
  }
768
- elementHtml = '<form name="' + scope.topLevelFormName + '" class="' + convertFormStyleToClass(attrs.formstyle) + ' novalidate"' + customAttrs + '>';
1001
+ var tag = attrs.forceform ? 'ng-form' : 'form';
1002
+ elementHtml = "<".concat(tag, " name=\"").concat(scope.topLevelFormName, "\" class=\"").concat(convertFormStyleToClass(attrs.formstyle), "\" novalidate ").concat(customAttrs, ">");
769
1003
  }
770
1004
  if (theRecord === scope.topLevelFormName) {
771
1005
  throw new Error('Model and Name must be distinct - they are both ' + theRecord);
@@ -781,10 +1015,12 @@ var fng;
781
1015
  // If we have modelControllers we need to let them know when we have form + data
782
1016
  var sharedData = scope[attrs.shared || 'sharedData'];
783
1017
  var modelControllers_1 = sharedData ? sharedData.modelControllers : [];
784
- if (subkeys.length > 0 || modelControllers_1.length > 0) {
1018
+ if ((subkeys.length > 0 || modelControllers_1.length > 0) && !scope.phaseWatcher) {
785
1019
  var unwatch2 = scope.$watch('phase', function (newValue) {
786
- if (newValue === 'ready') {
1020
+ scope.phaseWatcher = true;
1021
+ if (newValue === 'ready' && typeof unwatch2 === "function") {
787
1022
  unwatch2();
1023
+ unwatch2 = null;
788
1024
  // Tell the 'model controllers' that the form and data are there
789
1025
  for (var i = 0; i < modelControllers_1.length; i++) {
790
1026
  if (modelControllers_1[i].onAllReady) {
@@ -813,7 +1049,7 @@ var fng;
813
1049
  arrayOffset = scope[arrayToProcess[thisOffset].selectFunc](theRecord, info);
814
1050
  }
815
1051
  else if (arrayToProcess[thisOffset].keyList) {
816
- // We are chosing the array element by matching one or more keys
1052
+ // We are choosing the array element by matching one or more keys
817
1053
  var thisSubkeyList = arrayToProcess[thisOffset].keyList;
818
1054
  for (arrayOffset = 0; arrayOffset < dataVal.length; arrayOffset++) {
819
1055
  matching = true;
@@ -867,7 +1103,7 @@ var fng;
867
1103
  }
868
1104
  });
869
1105
  }
870
- $rootScope.$broadcast('formInputDone');
1106
+ $rootScope.$broadcast('formInputDone', attrs.name);
871
1107
  if (formGenerator.updateDataDependentDisplay && theRecord && Object.keys(theRecord).length > 0) {
872
1108
  // If this is not a test force the data dependent updates to the DOM
873
1109
  formGenerator.updateDataDependentDisplay(theRecord, null, true, scope);
@@ -916,7 +1152,7 @@ var fng;
916
1152
  /*@ngInject*/
917
1153
  SearchCtrl.$inject = ["$scope", "$http", "$location", "routingService"];
918
1154
  function SearchCtrl($scope, $http, $location, routingService) {
919
- var currentRequest = '';
1155
+ var lastSearchSent;
920
1156
  var _isNotMobile;
921
1157
  _isNotMobile = (function () {
922
1158
  var check = false;
@@ -971,9 +1207,15 @@ var fng;
971
1207
  };
972
1208
  $scope.selectResult = function (resultNo) {
973
1209
  var result = $scope.results[resultNo];
974
- var newURL = routingService.prefix() + '/' + result.resource + '/' + result.id + '/edit';
975
- if (result.resourceTab) {
976
- newURL += '/' + result.resourceTab;
1210
+ var newURL;
1211
+ if (result.url) {
1212
+ newURL = result.url.replace('|id|', result.id);
1213
+ }
1214
+ else {
1215
+ newURL = routingService.prefix() + '/' + result.resource + '/' + result.id + '/edit';
1216
+ if (result.resourceTab) {
1217
+ newURL += '/' + result.resourceTab;
1218
+ }
977
1219
  }
978
1220
  $location.url(newURL);
979
1221
  };
@@ -992,12 +1234,12 @@ var fng;
992
1234
  };
993
1235
  $scope.$watch('searchTarget', function (newValue) {
994
1236
  if (newValue && newValue.length > 0) {
995
- currentRequest = newValue;
996
- $http.get('/api/search?q=' + newValue).then(function (response) {
1237
+ lastSearchSent = $scope.testTime || new Date().valueOf();
1238
+ $http.get('/api/search?q=' + newValue + '&sentAt=' + lastSearchSent).then(function (response) {
997
1239
  var data = response.data;
998
1240
  // Check that we haven't fired off a subsequent request, in which
999
1241
  // case we are no longer interested in these results
1000
- if (currentRequest === newValue) {
1242
+ if (!data.timestamps || !data.timestamps.sentAt || Number.parseInt(data.timestamps.sentAt) === lastSearchSent) {
1001
1243
  if ($scope.searchTarget.length > 0) {
1002
1244
  $scope.results = data.results;
1003
1245
  $scope.moreCount = data.moreCount;
@@ -1064,6 +1306,21 @@ var fng;
1064
1306
  })(fng || (fng = {}));
1065
1307
  /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
1066
1308
  var fng;
1309
+ (function (fng) {
1310
+ var filters;
1311
+ (function (filters) {
1312
+ /*@ngInject*/
1313
+ function extractTimestampFromMongoID() {
1314
+ return function (id) {
1315
+ var timestamp = id.substring(0, 8);
1316
+ return new Date(parseInt(timestamp, 16) * 1000);
1317
+ };
1318
+ }
1319
+ filters.extractTimestampFromMongoID = extractTimestampFromMongoID;
1320
+ })(filters = fng.filters || (fng.filters = {}));
1321
+ })(fng || (fng = {}));
1322
+ /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
1323
+ var fng;
1067
1324
  (function (fng) {
1068
1325
  var filters;
1069
1326
  (function (filters) {
@@ -1244,6 +1501,7 @@ var fng;
1244
1501
  controllerName += 'Ctrl';
1245
1502
  locals.$scope = sharedData.modelControllers[level] = localScope;
1246
1503
  var parentScope = localScope.$parent;
1504
+ parentScope.items = parentScope.items || [];
1247
1505
  var addMenuOptions = function (array) {
1248
1506
  angular.forEach(array, function (value) {
1249
1507
  if (value.divider) {
@@ -1275,6 +1533,11 @@ var fng;
1275
1533
  locals.$scope.contextMenuPromise.then(function (array) { return addMenuOptions(array); });
1276
1534
  }
1277
1535
  }
1536
+ if (sharedData.modelNameDisplayPromise) {
1537
+ sharedData.modelNameDisplayPromise.then(function (value) {
1538
+ parentScope.modelNameDisplay = value;
1539
+ });
1540
+ }
1278
1541
  }
1279
1542
  catch (error) {
1280
1543
  // Check to see if error is no such controller - don't care
@@ -1304,7 +1567,7 @@ var fng;
1304
1567
  routing: 'ngroute',
1305
1568
  prefix: '' // How do we want to prefix our routes? If not empty string then first character must be slash (which is added if not)
1306
1569
  };
1307
- var postActions = ['edit'];
1570
+ var postActions = ['edit', 'view'];
1308
1571
  var builtInRoutes = [
1309
1572
  {
1310
1573
  route: '/analyse/:model/:reportSchemaName',
@@ -1314,12 +1577,19 @@ var fng;
1314
1577
  { route: '/analyse/:model', state: 'analyse::model', templateUrl: 'base-analysis.html' },
1315
1578
  { route: '/:model/:id/edit', state: 'model::edit', templateUrl: 'base-edit.html' },
1316
1579
  { route: '/:model/:id/edit/:tab', state: 'model::edit::tab', templateUrl: 'base-edit.html' },
1580
+ { route: '/:model/:id/view', state: 'model::edit', templateUrl: 'base-view.html' },
1581
+ { route: '/:model/:id/view/:tab', state: 'model::view::tab', templateUrl: 'base-view.html' },
1317
1582
  { route: '/:model/new', state: 'model::new', templateUrl: 'base-edit.html' },
1318
1583
  { route: '/:model', state: 'model::list', templateUrl: 'base-list.html' },
1584
+ { route: '/:model/viewonly', state: 'model::view', templateUrl: 'base-list-view.html' },
1585
+ // Non default form (subset of fields etc)
1319
1586
  { route: '/:model/:form/:id/edit', state: 'model::form::edit', templateUrl: 'base-edit.html' },
1320
1587
  { route: '/:model/:form/:id/edit/:tab', state: 'model::form::edit::tab', templateUrl: 'base-edit.html' },
1588
+ { route: '/:model/:form/:id/view', state: 'model::form::view', templateUrl: 'base-view.html' },
1589
+ { route: '/:model/:form/:id/view/:tab', state: 'model::form::view::tab', templateUrl: 'base-view.html' },
1321
1590
  { route: '/:model/:form/new', state: 'model::form::new', templateUrl: 'base-edit.html' },
1322
- { route: '/:model/:form', state: 'model::form::list', templateUrl: 'base-list.html' } // list page with links to non default form
1591
+ { route: '/:model/:form', state: 'model::form::list', templateUrl: 'base-list.html' },
1592
+ { route: '/:model/:form/viewonly', state: 'model::form::list::view', templateUrl: 'base-list-view.html' } // list page with edit links to non default form
1323
1593
  ];
1324
1594
  var _routeProvider, _stateProvider;
1325
1595
  var lastRoute = null;
@@ -1373,6 +1643,12 @@ var fng;
1373
1643
  case 'edit':
1374
1644
  urlStr = modelString + formString + '/' + id + '/edit' + tabString;
1375
1645
  break;
1646
+ case 'view':
1647
+ urlStr = modelString + formString + '/' + id + '/view' + tabString;
1648
+ break;
1649
+ case 'read':
1650
+ urlStr = modelString + formString + '/' + id + '/read' + tabString;
1651
+ break;
1376
1652
  case 'new':
1377
1653
  urlStr = modelString + formString + '/new' + tabString;
1378
1654
  break;
@@ -1455,8 +1731,9 @@ var fng;
1455
1731
  lastObject.modelName = locationSplit[1];
1456
1732
  var lastParts_1 = [locationSplit[locationParts - 1], locationSplit[locationParts - 2]];
1457
1733
  var newPos = lastParts_1.indexOf('new');
1734
+ var viewonlyPos = lastParts_1.indexOf('viewonly');
1458
1735
  var actionPos = void 0;
1459
- if (newPos === -1) {
1736
+ if (newPos === -1 && viewonlyPos === -1) {
1460
1737
  actionPos = postActions.reduce(function (previousValue, currentValue) {
1461
1738
  var pos = lastParts_1.indexOf(currentValue);
1462
1739
  return pos > -1 ? pos : previousValue;
@@ -1466,10 +1743,13 @@ var fng;
1466
1743
  lastObject.id = locationSplit[locationParts];
1467
1744
  }
1468
1745
  }
1469
- else {
1746
+ else if (newPos !== -1) {
1470
1747
  lastObject.newRecord = true;
1471
1748
  locationParts -= (1 + newPos);
1472
1749
  }
1750
+ else {
1751
+ locationParts -= (1 + viewonlyPos);
1752
+ }
1473
1753
  if (actionPos === 1 || newPos === 1) {
1474
1754
  lastObject.tab = lastParts_1[0];
1475
1755
  }
@@ -1480,34 +1760,6 @@ var fng;
1480
1760
  }
1481
1761
  return lastObject;
1482
1762
  };
1483
- ///**
1484
- // * DominicBoettger wrote:
1485
- // *
1486
- // * Parser for the states provided by ui.router
1487
- // */
1488
- //'use strict';
1489
- //formsAngular.factory('$stateParse', [function () {
1490
- //
1491
- // var lastObject = {};
1492
- //
1493
- // return function (state) {
1494
- // if (state.current && state.current.name) {
1495
- // lastObject = {newRecord: false};
1496
- // lastObject.modelName = state.params.model;
1497
- // if (state.current.name === 'model::list') {
1498
- // lastObject = {index: true};
1499
- // lastObject.modelName = state.params.model;
1500
- // } else if (state.current.name === 'model::edit') {
1501
- // lastObject.id = state.params.id;
1502
- // } else if (state.current.name === 'model::new') {
1503
- // lastObject.newRecord = true;
1504
- // } else if (state.current.name === 'model::analyse') {
1505
- // lastObject.analyse = true;
1506
- // }
1507
- // }
1508
- // return lastObject;
1509
- // };
1510
- //}]);
1511
1763
  },
1512
1764
  buildUrl: function (path) {
1513
1765
  var url = config.html5Mode ? '' : '#';
@@ -1524,46 +1776,25 @@ var fng;
1524
1776
  },
1525
1777
  redirectTo: function () {
1526
1778
  return function (operation, scope, location, id, tab) {
1527
- // switch (config.routing) {
1528
- // case 'ngroute' :
1529
- if (location.search()) {
1530
- location.url(location.path());
1779
+ location.search({}); // Lose any search parameters
1780
+ var urlStr;
1781
+ if (operation === 'onDelete') {
1782
+ if (config.onDelete) {
1783
+ if (config.onDelete === 'new') {
1784
+ urlStr = _buildOperationUrl(config.prefix, 'new', scope.modelName, scope.formName, id, tab);
1785
+ }
1786
+ else {
1787
+ urlStr = config.onDelete;
1788
+ }
1789
+ }
1790
+ else {
1791
+ urlStr = _buildOperationUrl(config.prefix, 'list', scope.modelName, scope.formName, id, tab);
1792
+ }
1793
+ }
1794
+ else {
1795
+ urlStr = _buildOperationUrl(config.prefix, operation, scope.modelName, scope.formName, id, tab);
1531
1796
  }
1532
- var urlStr = _buildOperationUrl(config.prefix, operation, scope.modelName, scope.formName, id, tab);
1533
1797
  location.path(urlStr);
1534
- // break;
1535
- // case 'uirouter' :
1536
- // var formString = scope.formName ? ('/' + scope.formName) : '';
1537
- // var modelString = config.prefix + '/' + scope.modelName;
1538
- // console.log('form schemas not supported with ui-router');
1539
- // switch (operation) {
1540
- // case 'list' :
1541
- // location.path(modelString + formString);
1542
- // break;
1543
- // case 'edit' :
1544
- // location.path(modelString + formString + '/' + id + '/edit');
1545
- // break;
1546
- // case 'new' :
1547
- // location.path(modelString + formString + '/new');
1548
- // break;
1549
- // }
1550
- // switch (operation) {
1551
- // case 'list' :
1552
- // $state.go('model::list', { model: model });
1553
- // break;
1554
- // case 'edit' :
1555
- // location.path('/' + scope.modelName + formString + '/' + id + '/edit');
1556
- // break;
1557
- // case 'new' :
1558
- // location.path('/' + scope.modelName + formString + '/new');
1559
- // break;
1560
- // }
1561
- // break;
1562
- //
1563
- //
1564
- // // edit: $state.go('model::edit', {id: data._id, model: $scope.modelName });
1565
- // // new: $state.go('model::new', {model: $scope.modelName});
1566
- // break;
1567
1798
  };
1568
1799
  }
1569
1800
  };
@@ -1585,7 +1816,7 @@ var fng;
1585
1816
  * All methods should be state-less
1586
1817
  *
1587
1818
  */
1588
- function formGenerator($location, $timeout, $filter, SubmissionsService, routingService, recordHandler) {
1819
+ function formGenerator($location, $timeout, $filter, routingService, recordHandler) {
1589
1820
  function handleSchema(description, source, destForm, destList, prefix, doRecursion, $scope, ctrlState) {
1590
1821
  function handletabInfo(tabName, thisInst) {
1591
1822
  var tabTitle = angular.copy(tabName);
@@ -1611,50 +1842,55 @@ var fng;
1611
1842
  }
1612
1843
  tab.content.push(thisInst);
1613
1844
  }
1845
+ if (typeof $scope.onSchemaFetch === "function") {
1846
+ $scope.onSchemaFetch(description, source);
1847
+ }
1614
1848
  for (var field in source) {
1615
- if (field === '_id') {
1616
- if (destList && source['_id'].options && source['_id'].options.list) {
1617
- handleListInfo(destList, source['_id'].options.list, field);
1618
- }
1619
- }
1620
- else if (source.hasOwnProperty(field)) {
1621
- var mongooseType = source[field], mongooseOptions = mongooseType.options || {};
1622
- var formData = mongooseOptions.form || {};
1623
- if (mongooseType.schema && !formData.hidden) {
1624
- if (doRecursion && destForm) {
1625
- var schemaSchema = [];
1626
- handleSchema('Nested ' + field, mongooseType.schema, schemaSchema, null, field + '.', true, $scope, ctrlState);
1627
- var sectionInstructions = basicInstructions(field, formData, prefix);
1628
- sectionInstructions.schema = schemaSchema;
1629
- if (formData.tab) {
1630
- handletabInfo(formData.tab, sectionInstructions);
1631
- }
1632
- if (formData.order !== undefined) {
1633
- destForm.splice(formData.order, 0, sectionInstructions);
1634
- }
1635
- else {
1636
- destForm.push(sectionInstructions);
1637
- }
1849
+ if (source.hasOwnProperty(field)) {
1850
+ if (field === '_id') {
1851
+ if (destList && source['_id'].options && source['_id'].options.list) {
1852
+ handleListInfo(destList, source['_id'].options.list, field);
1638
1853
  }
1639
1854
  }
1640
- else {
1641
- if (destForm && !formData.hidden) {
1642
- var formInstructions = basicInstructions(field, formData, prefix);
1643
- if (handleConditionals(formInstructions.showIf, formInstructions.name, $scope) && field !== 'options') {
1644
- var formInst = handleFieldType(formInstructions, mongooseType, mongooseOptions, $scope, ctrlState);
1645
- if (formInst.tab) {
1646
- handletabInfo(formInst.tab, formInst);
1855
+ else if (source.hasOwnProperty(field)) {
1856
+ var mongooseType = source[field], mongooseOptions = mongooseType.options || {};
1857
+ var formData = mongooseOptions.form || {};
1858
+ if (mongooseType.schema && !formData.hidden) {
1859
+ if (doRecursion && destForm) {
1860
+ var schemaSchema = [];
1861
+ handleSchema('Nested ' + field, mongooseType.schema, schemaSchema, null, field + '.', true, $scope, ctrlState);
1862
+ var sectionInstructions = basicInstructions(field, formData, prefix);
1863
+ sectionInstructions.schema = schemaSchema;
1864
+ if (formData.tab) {
1865
+ handletabInfo(formData.tab, sectionInstructions);
1647
1866
  }
1648
1867
  if (formData.order !== undefined) {
1649
- destForm.splice(formData.order, 0, formInst);
1868
+ destForm.splice(formData.order, 0, sectionInstructions);
1650
1869
  }
1651
1870
  else {
1652
- destForm.push(formInst);
1871
+ destForm.push(sectionInstructions);
1653
1872
  }
1654
1873
  }
1655
1874
  }
1656
- if (destList && mongooseOptions.list) {
1657
- handleListInfo(destList, mongooseOptions.list, field);
1875
+ else {
1876
+ if (destForm && !formData.hidden) {
1877
+ var formInstructions = basicInstructions(field, formData, prefix);
1878
+ if (handleConditionals(formInstructions.showIf, formInstructions.name, $scope) && field !== 'options') {
1879
+ var formInst = handleFieldType(formInstructions, mongooseType, mongooseOptions, $scope, ctrlState);
1880
+ if (formInst.tab) {
1881
+ handletabInfo(formInst.tab, formInst);
1882
+ }
1883
+ if (formData.order !== undefined) {
1884
+ destForm.splice(formData.order, 0, formInst);
1885
+ }
1886
+ else {
1887
+ destForm.push(formInst);
1888
+ }
1889
+ }
1890
+ }
1891
+ if (destList && mongooseOptions.list) {
1892
+ handleListInfo(destList, mongooseOptions.list, field);
1893
+ }
1658
1894
  }
1659
1895
  }
1660
1896
  }
@@ -1678,6 +1914,9 @@ var fng;
1678
1914
  // console.log($scope.tabs[0]['title'])
1679
1915
  // $location.hash($scope.tabs[0]['title']);
1680
1916
  // }
1917
+ if (typeof $scope.onSchemaProcessed === "function") {
1918
+ $scope.onSchemaProcessed(description, description.slice(0, 5) === 'Main ' ? $scope.baseSchema() : destForm);
1919
+ }
1681
1920
  if (destList && destList.length === 0) {
1682
1921
  handleEmptyList(description, destList, destForm, source);
1683
1922
  }
@@ -1686,23 +1925,23 @@ var fng;
1686
1925
  function performLookupSelect() {
1687
1926
  formInstructions.options = recordHandler.suffixCleanId(formInstructions, 'Options');
1688
1927
  formInstructions.ids = recordHandler.suffixCleanId(formInstructions, '_ids');
1689
- if (!formInstructions.hidden && mongooseOptions.ref) {
1690
- if (typeof mongooseOptions.ref === 'string') {
1691
- mongooseOptions.ref = { type: 'lookup', collection: mongooseOptions.ref };
1692
- console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(mongooseOptions.ref));
1693
- }
1694
- switch (mongooseOptions.ref.type) {
1695
- case 'lookup':
1696
- recordHandler.setUpLookupOptions(mongooseOptions.ref.collection, formInstructions, $scope, ctrlState, handleSchema);
1697
- break;
1698
- case 'lookupList':
1699
- recordHandler.setUpLookupListOptions(mongooseOptions.ref, formInstructions, $scope, ctrlState);
1700
- break;
1701
- case 'internal':
1702
- recordHandler.handleInternalLookup($scope, formInstructions, mongooseOptions.ref);
1703
- break;
1704
- default:
1705
- throw new Error("Unsupported ref type " + mongooseOptions.ref.type + " found in " + formInstructions.name);
1928
+ if (!formInstructions.hidden) {
1929
+ if (mongooseOptions.ref) {
1930
+ recordHandler.setUpLookupOptions(mongooseOptions.ref, formInstructions, $scope, ctrlState, handleSchema);
1931
+ }
1932
+ else if (mongooseOptions.lookupListRef) {
1933
+ recordHandler.setUpLookupListOptions(mongooseOptions.lookupListRef, formInstructions, $scope, ctrlState);
1934
+ formInstructions.lookupListRef = mongooseOptions.lookupListRef;
1935
+ }
1936
+ else if (mongooseOptions.internalRef) {
1937
+ recordHandler.handleInternalLookup($scope, formInstructions, mongooseOptions.internalRef);
1938
+ formInstructions.internalRef = mongooseOptions.internalRef;
1939
+ }
1940
+ else if (mongooseOptions.customLookupOptions) {
1941
+ // nothing to do - call setUpCustomLookupOptions() when ready to provide id and option arrays
1942
+ }
1943
+ else {
1944
+ throw new Error("No supported select lookup type found in ".concat(formInstructions.name));
1706
1945
  }
1707
1946
  }
1708
1947
  }
@@ -1736,13 +1975,22 @@ var fng;
1736
1975
  }
1737
1976
  else if (mongooseType.instance === 'ObjectID') {
1738
1977
  formInstructions.ref = mongooseOptions.ref;
1739
- if (formInstructions.link && formInstructions.link.linkOnly) {
1740
- formInstructions.type = 'link';
1741
- formInstructions.linkText = formInstructions.link.text;
1978
+ if (formInstructions.link) {
1979
+ if (formInstructions.link.linkOnly) {
1980
+ formInstructions.type = 'link';
1981
+ formInstructions.linktext = formInstructions.link.text;
1982
+ }
1983
+ else if (formInstructions.link.label) {
1984
+ formInstructions.linklabel = true;
1985
+ }
1986
+ else {
1987
+ console.log('Unsupported link setup');
1988
+ }
1742
1989
  formInstructions.form = formInstructions.link.form;
1990
+ formInstructions.linktab = formInstructions.link.linktab;
1743
1991
  delete formInstructions.link;
1744
1992
  }
1745
- else {
1993
+ if (formInstructions.type !== 'link') {
1746
1994
  formInstructions.type = 'select';
1747
1995
  if (formInstructions.select2 || (mongooseOptions.form && mongooseOptions.form.select2)) {
1748
1996
  console.log('support for fng-select2 has been removed in 0.8.3 - please convert to fng-ui-select');
@@ -1754,16 +2002,18 @@ var fng;
1754
2002
  }
1755
2003
  else if (mongooseType.instance === 'Date') {
1756
2004
  if (!formInstructions.type) {
2005
+ formInstructions.intType = 'date';
1757
2006
  if (formInstructions.readonly) {
1758
2007
  formInstructions.type = 'text';
1759
2008
  }
1760
2009
  else if (formInstructions.directive) {
1761
- formInstructions.type = 'text'; // Think they all use date
2010
+ formInstructions.type = 'text';
1762
2011
  }
1763
2012
  else {
1764
2013
  try {
1765
2014
  formInstructions.add = formInstructions.add || '';
1766
- var testDatePickerInstalled = angular.module('ui.date').requires;
2015
+ // Check whether DatePicker is installed
2016
+ angular.module('ui.date').requires;
1767
2017
  formInstructions.type = 'text';
1768
2018
  formInstructions.add += ' ui-date ui-date-format ';
1769
2019
  // formInstructions.add += ' ui-date ui-date-format datepicker-popup-fix ';
@@ -1776,10 +2026,10 @@ var fng;
1776
2026
  }
1777
2027
  }
1778
2028
  else if (mongooseType.instance.toLowerCase() === 'boolean') {
1779
- formInstructions.type = 'checkbox';
2029
+ formInstructions.type = formInstructions.type || 'checkbox';
1780
2030
  }
1781
2031
  else if (mongooseType.instance === 'Number') {
1782
- formInstructions.type = 'number';
2032
+ formInstructions.type = formInstructions.type || 'number';
1783
2033
  if (mongooseOptions.min !== undefined) {
1784
2034
  formInstructions.add = 'min="' + mongooseOptions.min + '" ' + (formInstructions.add || '');
1785
2035
  }
@@ -1799,11 +2049,17 @@ var fng;
1799
2049
  if (mongooseOptions.readonly) {
1800
2050
  formInstructions['readonly'] = true;
1801
2051
  }
2052
+ if (mongooseType.defaultValue !== undefined) {
2053
+ formInstructions.defaultValue = mongooseType.defaultValue;
2054
+ }
2055
+ else if (mongooseType.options && mongooseType.options.default !== undefined) {
2056
+ console.log('No support for default with no value, yet');
2057
+ }
1802
2058
  return formInstructions;
1803
2059
  }
1804
- function getArrayFieldToExtend(fieldName, $scope) {
2060
+ function getArrayFieldToExtend(fieldName, $scope, modelOverride) {
1805
2061
  var fieldParts = fieldName.split('.');
1806
- var arrayField = $scope.record;
2062
+ var arrayField = modelOverride || $scope.record;
1807
2063
  for (var i = 0, l = fieldParts.length; i < l; i++) {
1808
2064
  if (!arrayField[fieldParts[i]]) {
1809
2065
  if (i === l - 1) {
@@ -1920,6 +2176,9 @@ var fng;
1920
2176
  generateEditUrl: function generateEditUrl(obj, $scope) {
1921
2177
  return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/edit');
1922
2178
  },
2179
+ generateViewUrl: function generateViewUrl(obj, $scope) {
2180
+ return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + obj._id + '/view');
2181
+ },
1923
2182
  generateNewUrl: function generateNewUrl($scope) {
1924
2183
  return routingService.buildUrl($scope.modelName + '/' + ($scope.formName ? $scope.formName + '/' : '') + 'new');
1925
2184
  },
@@ -2004,25 +2263,44 @@ var fng;
2004
2263
  }
2005
2264
  return forceNextTime;
2006
2265
  },
2007
- add: function add(fieldName, $event, $scope) {
2008
- var arrayField = getArrayFieldToExtend(fieldName, $scope);
2009
- arrayField.push({});
2010
- $scope.setFormDirty($event);
2266
+ add: function add(fieldName, $event, $scope, modelOverride) {
2267
+ var _a;
2268
+ // check that target element is visible. May not be reliable - see https://stackoverflow.com/questions/19669786/check-if-element-is-visible-in-dom
2269
+ if ($event.target.offsetParent) {
2270
+ var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2271
+ var schemaElement = $scope.formSchema.find(function (f) { return f.name === fieldName; }); // In case someone is using the formSchema directly
2272
+ var subSchema = schemaElement ? schemaElement.schema : null;
2273
+ var obj = subSchema ? $scope.setDefaults(subSchema, fieldName + '.') : {};
2274
+ if (typeof ((_a = $scope.dataEventFunctions) === null || _a === void 0 ? void 0 : _a.onInitialiseNewSubDoc) === "function") {
2275
+ $scope.dataEventFunctions.onInitialiseNewSubDoc(fieldName, subSchema, obj);
2276
+ }
2277
+ arrayField.push(obj);
2278
+ $scope.setFormDirty($event);
2279
+ }
2011
2280
  },
2012
- unshift: function unshift(fieldName, $event, $scope) {
2013
- var arrayField = getArrayFieldToExtend(fieldName, $scope);
2281
+ unshift: function unshift(fieldName, $event, $scope, modelOverride) {
2282
+ var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2014
2283
  arrayField.unshift({});
2015
2284
  $scope.setFormDirty($event);
2016
2285
  },
2017
- remove: function remove(fieldName, value, $event, $scope) {
2286
+ remove: function remove(fieldName, value, $event, $scope, modelOverride) {
2018
2287
  // Remove an element from an array
2019
- var fieldParts = fieldName.split('.');
2020
- var arrayField = $scope.record;
2021
- for (var i = 0, l = fieldParts.length; i < l; i++) {
2022
- arrayField = arrayField[fieldParts[i]];
2288
+ var arrayField = getArrayFieldToExtend(fieldName, $scope, modelOverride);
2289
+ var err;
2290
+ if (typeof $scope.dataEventFunctions.onDeleteSubDoc === "function") {
2291
+ var schemaElement = $scope.formSchema.find(function (f) {
2292
+ return f.name === fieldName;
2293
+ });
2294
+ var subSchema = schemaElement ? schemaElement.schema : null;
2295
+ err = $scope.dataEventFunctions.onDeleteSubDoc(fieldName, subSchema, arrayField, value);
2296
+ }
2297
+ if (err) {
2298
+ $scope.showError(err);
2299
+ }
2300
+ else {
2301
+ arrayField.splice(value, 1);
2302
+ $scope.setFormDirty($event);
2023
2303
  }
2024
- arrayField.splice(value, 1);
2025
- $scope.setFormDirty($event);
2026
2304
  },
2027
2305
  hasError: function hasError(formName, name, index, $scope) {
2028
2306
  var result = false;
@@ -2034,7 +2312,7 @@ var fng;
2034
2312
  // Cannot assume that directives will use the same methods
2035
2313
  if (form) {
2036
2314
  var field_1 = form[name];
2037
- if (field_1 && field_1.$invalid) {
2315
+ if (field_1 && field_1.$invalid && !field_1.$$attr.readonly) {
2038
2316
  if (field_1.$dirty) {
2039
2317
  result = true;
2040
2318
  }
@@ -2074,6 +2352,9 @@ var fng;
2074
2352
  $scope.generateEditUrl = function (obj) {
2075
2353
  return formGeneratorInstance.generateEditUrl(obj, $scope);
2076
2354
  };
2355
+ $scope.generateViewUrl = function (obj) {
2356
+ return formGeneratorInstance.generateViewUrl(obj, $scope);
2357
+ };
2077
2358
  $scope.generateNewUrl = function () {
2078
2359
  return formGeneratorInstance.generateNewUrl($scope);
2079
2360
  };
@@ -2081,7 +2362,7 @@ var fng;
2081
2362
  return recordHandlerInstance.scrollTheList($scope);
2082
2363
  };
2083
2364
  $scope.getListData = function (record, fieldName) {
2084
- return recordHandlerInstance.getListData($scope, record, fieldName, $scope.listSchema);
2365
+ return recordHandlerInstance.getListData(record, fieldName, $scope.listSchema, $scope);
2085
2366
  };
2086
2367
  $scope.setPristine = function (clearErrors) {
2087
2368
  if (clearErrors) {
@@ -2103,17 +2384,17 @@ var fng;
2103
2384
  console.log('setFormDirty called without an event (fine in a unit test)');
2104
2385
  }
2105
2386
  };
2106
- $scope.add = function (fieldName, $event) {
2107
- return formGeneratorInstance.add(fieldName, $event, $scope);
2387
+ $scope.add = function (fieldName, $event, modelOverride) {
2388
+ return formGeneratorInstance.add(fieldName, $event, $scope, modelOverride);
2108
2389
  };
2109
2390
  $scope.hasError = function (form, name, index) {
2110
2391
  return formGeneratorInstance.hasError(form, name, index, $scope);
2111
2392
  };
2112
- $scope.unshift = function (fieldName, $event) {
2113
- return formGeneratorInstance.unshift(fieldName, $event, $scope);
2393
+ $scope.unshift = function (fieldName, $event, modelOverride) {
2394
+ return formGeneratorInstance.unshift(fieldName, $event, $scope, modelOverride);
2114
2395
  };
2115
- $scope.remove = function (fieldName, value, $event) {
2116
- return formGeneratorInstance.remove(fieldName, value, $event, $scope);
2396
+ $scope.remove = function (fieldName, value, $event, modelOverride) {
2397
+ return formGeneratorInstance.remove(fieldName, value, $event, $scope, modelOverride);
2117
2398
  };
2118
2399
  $scope.baseSchema = function () {
2119
2400
  return ($scope.tabs.length ? $scope.tabs : $scope.formSchema);
@@ -2128,7 +2409,7 @@ var fng;
2128
2409
  };
2129
2410
  }
2130
2411
  services.formGenerator = formGenerator;
2131
- formGenerator.$inject = ["$location", "$timeout", "$filter", "SubmissionsService", "routingService", "recordHandler"];
2412
+ formGenerator.$inject = ["$location", "$timeout", "$filter", "routingService", "recordHandler"];
2132
2413
  })(services = fng.services || (fng.services = {}));
2133
2414
  })(fng || (fng = {}));
2134
2415
  /// <reference path="../../index.d.ts" />
@@ -2137,8 +2418,8 @@ var fng;
2137
2418
  var services;
2138
2419
  (function (services) {
2139
2420
  /*@ngInject*/
2140
- formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService"];
2141
- function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService) {
2421
+ formMarkupHelper.$inject = ["cssFrameworkService", "inputSizeHelper", "addAllService", "$filter"];
2422
+ function formMarkupHelper(cssFrameworkService, inputSizeHelper, addAllService, $filter) {
2142
2423
  function generateNgShow(showWhen, model) {
2143
2424
  function evaluateSide(side) {
2144
2425
  var result = side;
@@ -2166,11 +2447,15 @@ var fng;
2166
2447
  }
2167
2448
  return evaluateSide(showWhen.lhs) + conditionSymbols[conditionPos] + evaluateSide(showWhen.rhs);
2168
2449
  }
2169
- var isHorizontalStyle = function isHorizontalStyle(formStyle) {
2170
- return (!formStyle || formStyle === 'undefined' || ['vertical', 'inline'].indexOf(formStyle) === -1);
2450
+ var isHorizontalStyle = function isHorizontalStyle(formStyle, includeStacked) {
2451
+ var exclude = ['vertical', 'inline'];
2452
+ if (!includeStacked) {
2453
+ exclude.push('stacked');
2454
+ }
2455
+ return (!formStyle || formStyle === 'undefined' || !exclude.includes(formStyle));
2171
2456
  };
2172
2457
  function glyphClass() {
2173
- return (cssFrameworkService.framework() === 'bs2') ? 'icon' : 'glyphicon glyphicon';
2458
+ return (cssFrameworkService.framework() === 'bs2' ? 'icon' : 'glyphicon glyphicon');
2174
2459
  }
2175
2460
  return {
2176
2461
  isHorizontalStyle: isHorizontalStyle,
@@ -2188,7 +2473,9 @@ var fng;
2188
2473
  insert += 'ng-show="' + generateNgShow(info.showWhen, options.model) + '"';
2189
2474
  }
2190
2475
  }
2191
- insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
2476
+ if (info.id && typeof info.id.replace === "function") {
2477
+ insert += ' id="cg_' + info.id.replace(/\./g, '-') + '"';
2478
+ }
2192
2479
  if (cssFrameworkService.framework() === 'bs3') {
2193
2480
  classes += ' form-group';
2194
2481
  if (options.formstyle === 'vertical' && info.size !== 'block-level') {
@@ -2213,7 +2500,7 @@ var fng;
2213
2500
  closeTag += '</div>';
2214
2501
  }
2215
2502
  else {
2216
- if (isHorizontalStyle(options.formstyle)) {
2503
+ if (isHorizontalStyle(options.formstyle, true)) {
2217
2504
  template += '<div' + addAllService.addAll(scope, 'Group', 'control-group', options);
2218
2505
  closeTag = '</div>';
2219
2506
  }
@@ -2227,11 +2514,13 @@ var fng;
2227
2514
  },
2228
2515
  label: function label(scope, fieldInfo, addButtonMarkup, options) {
2229
2516
  var labelHTML = '';
2230
- if ((cssFrameworkService.framework() === 'bs3' || (options.formstyle !== 'inline' && fieldInfo.label !== '')) || addButtonMarkup) {
2517
+ if ((cssFrameworkService.framework() === 'bs3' || (!['inline', 'stacked'].includes(options.formstyle) && fieldInfo.label !== '')) || addButtonMarkup) {
2231
2518
  labelHTML = '<label';
2232
2519
  var classes = 'control-label';
2233
- if (isHorizontalStyle(options.formstyle)) {
2234
- labelHTML += ' for="' + fieldInfo.id + '"';
2520
+ if (isHorizontalStyle(options.formstyle, false)) {
2521
+ if (!fieldInfo.linklabel) {
2522
+ labelHTML += ' for="' + fieldInfo.id + '"';
2523
+ }
2235
2524
  if (typeof fieldInfo.labelDefaultClass !== 'undefined') {
2236
2525
  // Override default label class (can be empty)
2237
2526
  classes += ' ' + fieldInfo.labelDefaultClass;
@@ -2240,7 +2529,7 @@ var fng;
2240
2529
  classes += ' col-sm-3';
2241
2530
  }
2242
2531
  }
2243
- else if (options.formstyle === 'inline') {
2532
+ else if (['inline', 'stacked'].includes(options.formstyle)) {
2244
2533
  labelHTML += ' for="' + fieldInfo.id + '"';
2245
2534
  classes += ' sr-only';
2246
2535
  }
@@ -2249,6 +2538,17 @@ var fng;
2249
2538
  labelHTML += ' <i id="add_' + fieldInfo.id + '" ng-click="add(\'' + fieldInfo.name + '\',$event)" class="' + glyphClass() + '-plus-sign"></i>';
2250
2539
  }
2251
2540
  labelHTML += '</label>';
2541
+ if (fieldInfo.linklabel) {
2542
+ var value = '<fng-link fld="' + fieldInfo.name + '" ref="' + fieldInfo.ref + '" text="' + escape(labelHTML) + '"';
2543
+ if (fieldInfo.form) {
2544
+ value += ' form="' + fieldInfo.form + '"';
2545
+ }
2546
+ if (fieldInfo.linktab) {
2547
+ value += ' linktab="' + fieldInfo.linktab + '"';
2548
+ }
2549
+ value += '></fng-link>';
2550
+ labelHTML = value;
2551
+ }
2252
2552
  }
2253
2553
  return labelHTML;
2254
2554
  },
@@ -2268,14 +2568,17 @@ var fng;
2268
2568
  else {
2269
2569
  sizeClassBS2 = (fieldInfo.size ? ' input-' + fieldInfo.size : '');
2270
2570
  }
2271
- if (options.formstyle === 'inline') {
2571
+ if (['inline', 'stacked'].includes(options.formstyle)) {
2272
2572
  placeHolder = placeHolder || fieldInfo.label;
2273
2573
  }
2274
- common = 'ng-model="' + modelString + '"' + (idString ? ' id="' + idString + '" name="' + idString + '" ' : ' name="' + nameString + '" ');
2574
+ common = 'data-ng-model="' + modelString + '"' + (idString ? ' id="' + idString + '" name="' + idString + '" ' : ' name="' + nameString + '" ');
2275
2575
  common += (placeHolder ? ('placeholder="' + placeHolder + '" ') : '');
2276
2576
  if (fieldInfo.popup) {
2277
2577
  common += 'title="' + fieldInfo.popup + '" ';
2278
2578
  }
2579
+ if (fieldInfo.ariaLabel) {
2580
+ common += 'aria-label="' + fieldInfo.ariaLabel + '" ';
2581
+ }
2279
2582
  common += addAllService.addAll(scope, 'Field', null, options);
2280
2583
  return {
2281
2584
  common: common,
@@ -2286,28 +2589,36 @@ var fng;
2286
2589
  };
2287
2590
  },
2288
2591
  inputChrome: function inputChrome(value, fieldInfo, options, markupVars) {
2289
- if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle) && fieldInfo.type !== 'checkbox') {
2592
+ if (cssFrameworkService.framework() === 'bs3' && isHorizontalStyle(options.formstyle, true) && fieldInfo.type !== 'checkbox') {
2290
2593
  value = '<div class="bs3-input ' + markupVars.sizeClassBS3 + '">' + value + '</div>';
2291
2594
  }
2292
2595
  // Hack to cope with inline help in directives
2293
2596
  var inlineHelp = (fieldInfo.helpInline || '') + (fieldInfo.helpinline || '');
2294
2597
  if (inlineHelp.length > 0) {
2295
- value += '<span class="' + (cssFrameworkService.framework() === 'bs2' ? 'help-inline' : 'help-block') + '">' + inlineHelp + '</span>';
2296
- }
2297
- // If we have chosen
2298
- value += '<div ng-if="' + (options.name || 'myForm') + '.' + fieldInfo.id + '.$dirty" class="help-block">' +
2299
- ' <div ng-messages="' + (options.name || 'myForm') + '.' + fieldInfo.id + '.$error">' +
2300
- ' <div ng-messages-include="error-messages.html">' +
2301
- ' </div>' +
2302
- ' </div>' +
2303
- '</div>';
2598
+ var helpMarkup = cssFrameworkService.framework() === 'bs2' ? { el: 'span', cl: 'help-inline' } : { el: 'div', cl: 'help-block' };
2599
+ value += "<".concat(helpMarkup.el, " class=\"").concat(helpMarkup.cl, "\">").concat(inlineHelp, "</").concat(helpMarkup.el, ">");
2600
+ }
2601
+ if (!options.noid) {
2602
+ value += "<div ng-if=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$dirty\" class=\"help-block\">") +
2603
+ " <div ng-messages=\"".concat((options.name || 'myForm'), "['").concat(fieldInfo.id, "'].$error\">") +
2604
+ ' <div ng-messages-include="error-messages.html">' +
2605
+ ' </div>' +
2606
+ ' </div>' +
2607
+ '</div>';
2608
+ }
2304
2609
  if (fieldInfo.help) {
2305
- value += '<span class="help-block">' + fieldInfo.help + '</span>';
2610
+ value += '<div class="help-block">' + fieldInfo.help + '</div>';
2306
2611
  }
2307
2612
  return value;
2308
2613
  },
2309
2614
  generateSimpleInput: function generateSimpleInput(common, fieldInfo, options) {
2310
- var result = '<input ' + common + 'type="' + fieldInfo.type + '"';
2615
+ var result = '<input ' + common + 'type="' + fieldInfo.type + '" ';
2616
+ if (!fieldInfo.label && !fieldInfo.ariaLabel) {
2617
+ result += "aria-label=\"".concat(fieldInfo.name.replace(/\./g, ' '), "\" ");
2618
+ }
2619
+ else if (options.subschema) {
2620
+ result += "aria-label=\"".concat(fieldInfo.label ? ($filter('titleCase')(options.subschemaroot) + ' ' + fieldInfo.label) : (fieldInfo.popup || fieldInfo.name.replace(/\./g, ' ')), "\" ");
2621
+ }
2311
2622
  if (options.formstyle === 'inline' && cssFrameworkService.framework() === 'bs2' && !fieldInfo.size) {
2312
2623
  result += 'class="input-small"';
2313
2624
  }
@@ -2316,7 +2627,7 @@ var fng;
2316
2627
  },
2317
2628
  controlDivClasses: function controlDivClasses(options) {
2318
2629
  var result = [];
2319
- if (isHorizontalStyle(options.formstyle)) {
2630
+ if (isHorizontalStyle(options.formstyle, false)) {
2320
2631
  result.push(cssFrameworkService.framework() === 'bs2' ? 'controls' : 'col-sm-9');
2321
2632
  }
2322
2633
  return result;
@@ -2350,7 +2661,13 @@ var fng;
2350
2661
  if (fieldInfo.add) {
2351
2662
  result += ' ' + fieldInfo.add + ' ';
2352
2663
  }
2353
- result += requiredStr + (fieldInfo.readonly ? ' readonly' : '') + ' ';
2664
+ result += requiredStr;
2665
+ if (fieldInfo.readonly) {
2666
+ result += " ".concat(typeof fieldInfo.readOnly === 'boolean' ? 'readonly' : 'ng-readonly="' + fieldInfo.readonly + '"', " ");
2667
+ }
2668
+ else {
2669
+ result += ' ';
2670
+ }
2354
2671
  return result;
2355
2672
  }
2356
2673
  };
@@ -2506,26 +2823,32 @@ var fng;
2506
2823
  *
2507
2824
  */
2508
2825
  /*@ngInject*/
2509
- recordHandler.$inject = ["$http", "$location", "$window", "$filter", "$timeout", "routingService", "SubmissionsService", "SchemasService"];
2510
- function recordHandler($http, $location, $window, $filter, $timeout, routingService, SubmissionsService, SchemasService) {
2826
+ recordHandler.$inject = ["$location", "$window", "$filter", "$timeout", "routingService", "cssFrameworkService", "SubmissionsService", "SchemasService"];
2827
+ function recordHandler($location, $window, $filter, $timeout, routingService, cssFrameworkService, SubmissionsService, SchemasService) {
2511
2828
  // TODO: Put this in a service
2512
2829
  var makeMongoId = function (rnd) {
2513
2830
  if (rnd === void 0) { rnd = function (r16) { return Math.floor(r16).toString(16); }; }
2514
- return rnd(Date.now() / 1000) + ' '.repeat(16).replace(/./g, function () { return rnd(Math.random() * 16); });
2831
+ return rnd(Date.now() / 1000) + " ".repeat(16).replace(/./g, function () { return rnd(Math.random() * 16); });
2515
2832
  };
2833
+ function _handleCancel(resp) {
2834
+ if (["cancel", "backdrop click", "escape key press"].indexOf(resp) === -1) {
2835
+ throw resp;
2836
+ }
2837
+ }
2516
2838
  var suffixCleanId = function suffixCleanId(inst, suffix) {
2517
- return (inst.id || 'f_' + inst.name).replace(/\./g, '_') + suffix;
2839
+ return (inst.id || "f_" + inst.name).replace(/\./g, "_") + suffix;
2518
2840
  };
2519
- var walkTree = function (object, fieldname, element) {
2841
+ var walkTree = function (object, fieldname, element, insertIntermediateObjects) {
2520
2842
  // Walk through subdocs to find the required key
2521
2843
  // for instance walkTree(master,'address.street.number',element)
2522
2844
  // called by getData and setData
2845
+ if (insertIntermediateObjects === void 0) { insertIntermediateObjects = false; }
2523
2846
  // element is used when accessing in the context of a input, as the id (like exams-2-grader)
2524
2847
  // gives us the element of an array (one level down only for now). Leaving element blank returns the whole array
2525
- var parts = fieldname.split('.'), higherLevels = parts.length - 1, workingRec = object;
2848
+ var parts = fieldname.split("."), higherLevels = parts.length - 1, workingRec = object;
2526
2849
  for (var i = 0; i < higherLevels; i++) {
2527
2850
  if (!workingRec) {
2528
- throw new Error("walkTree failed: Object = " + object + ", fieldname = " + fieldname + ", i = " + i);
2851
+ throw new Error("walkTree failed: Object = ".concat(object, ", fieldname = ").concat(fieldname, ", i = ").concat(i));
2529
2852
  }
2530
2853
  if (angular.isArray(workingRec)) {
2531
2854
  workingRec = _.map(workingRec, function (obj) {
@@ -2533,18 +2856,21 @@ var fng;
2533
2856
  });
2534
2857
  }
2535
2858
  else {
2859
+ if (insertIntermediateObjects && !workingRec[parts[i]]) {
2860
+ workingRec[parts[i]] = {};
2861
+ }
2536
2862
  workingRec = workingRec[parts[i]];
2537
2863
  }
2538
- if (angular.isArray(workingRec) && typeof element !== 'undefined') {
2539
- if (element.scope && typeof element.scope === 'function') {
2864
+ if (angular.isArray(workingRec) && typeof element !== "undefined") {
2865
+ if (element.scope && typeof element.scope === "function") {
2540
2866
  // If we come across an array we need to find the correct position, if we have an element
2541
2867
  workingRec = workingRec[element.scope().$index];
2542
2868
  }
2543
- else if (typeof element === 'number') {
2869
+ else if (typeof element === "number") {
2544
2870
  workingRec = workingRec[element];
2545
2871
  }
2546
2872
  else {
2547
- throw new Error('Unsupported element type in walkTree ' + fieldname);
2873
+ throw new Error("Unsupported element type in walkTree " + fieldname);
2548
2874
  }
2549
2875
  }
2550
2876
  if (!workingRec) {
@@ -2557,15 +2883,20 @@ var fng;
2557
2883
  };
2558
2884
  };
2559
2885
  var setData = function setData(object, fieldname, element, value) {
2560
- var leafData = walkTree(object, fieldname, element);
2886
+ var leafData = walkTree(object, fieldname, element, !!value);
2561
2887
  if (leafData.lastObject && leafData.key) {
2562
- if (angular.isArray(leafData.lastObject)) {
2563
- for (var i = 0; i < leafData.lastObject.length; i++) {
2564
- leafData.lastObject[i][leafData.key] = value[i];
2888
+ if (value) {
2889
+ if (angular.isArray(leafData.lastObject)) {
2890
+ for (var i = 0; i < leafData.lastObject.length; i++) {
2891
+ leafData.lastObject[i][leafData.key] = value[i];
2892
+ }
2893
+ }
2894
+ else {
2895
+ leafData.lastObject[leafData.key] = value;
2565
2896
  }
2566
2897
  }
2567
2898
  else {
2568
- leafData.lastObject[leafData.key] = value;
2899
+ delete leafData.lastObject[leafData.key];
2569
2900
  }
2570
2901
  }
2571
2902
  };
@@ -2584,11 +2915,17 @@ var fng;
2584
2915
  }
2585
2916
  return retVal;
2586
2917
  };
2587
- var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState) {
2918
+ var updateRecordWithLookupValues = function (schemaElement, $scope, ctrlState, ignoreDirty) {
2919
+ if (ignoreDirty === void 0) { ignoreDirty = false; }
2588
2920
  // Update the master and the record with the lookup values, master first
2589
- if (!$scope.topLevelFormName || $scope[$scope.topLevelFormName].$pristine) {
2921
+ if (!$scope.topLevelFormName || ($scope[$scope.topLevelFormName] && (ignoreDirty || $scope[$scope.topLevelFormName].$pristine))) {
2590
2922
  updateObject(schemaElement.name, ctrlState.master, function (value) {
2591
- return convertForeignKeys(schemaElement, value, $scope[suffixCleanId(schemaElement, 'Options')], $scope[suffixCleanId(schemaElement, '_ids')]);
2923
+ if (typeof value == "object" && value.id) {
2924
+ return value;
2925
+ }
2926
+ else {
2927
+ return convertForeignKeys(schemaElement, value, $scope[suffixCleanId(schemaElement, "Options")], $scope[suffixCleanId(schemaElement, "_ids")]);
2928
+ }
2592
2929
  });
2593
2930
  // Then copy the converted keys from master into record
2594
2931
  var newVal = getData(ctrlState.master, schemaElement.name);
@@ -2599,38 +2936,29 @@ var fng;
2599
2936
  };
2600
2937
  // Split a field name into the next level and all following levels
2601
2938
  function splitFieldName(aFieldName) {
2602
- var nesting = aFieldName.split('.'), result = [nesting[0]];
2939
+ var nesting = aFieldName.split("."), result = [nesting[0]];
2603
2940
  if (nesting.length > 1) {
2604
- result.push(nesting.slice(1).join('.'));
2941
+ result.push(nesting.slice(1).join("."));
2605
2942
  }
2606
2943
  return result;
2607
2944
  }
2608
- var getListData = function getListData($scope, record, fieldName, listSchema) {
2945
+ var getListData = function getListData(record, fieldName, listSchema, $scope) {
2609
2946
  if (listSchema === void 0) { listSchema = null; }
2610
- var retVal = record;
2611
- var nests = fieldName.split('.');
2612
- for (var i = 0; i < nests.length; i++) {
2613
- if (retVal !== undefined && retVal !== null && nests && nests[i]) {
2614
- retVal = retVal[nests[i]];
2615
- }
2616
- }
2617
- if (retVal === undefined) {
2618
- retVal = '';
2619
- }
2947
+ var retVal = getData(record, fieldName) || "";
2620
2948
  if (retVal && listSchema) {
2621
2949
  // Convert list fields as per instructions in params (ideally should be the same as what is found in data_form getListFields
2622
- var schemaElm = _.find(listSchema, function (elm) { return (elm['name'] === fieldName); });
2950
+ var schemaElm = _.find(listSchema, function (elm) { return (elm["name"] === fieldName); });
2623
2951
  if (schemaElm) {
2624
- switch (schemaElm['params']) {
2952
+ switch (schemaElm["params"]) {
2625
2953
  case undefined:
2626
2954
  break;
2627
- case 'timestamp':
2955
+ case "timestamp":
2628
2956
  var timestamp = retVal.toString().substring(0, 8);
2629
2957
  var date = new Date(parseInt(timestamp, 16) * 1000);
2630
- retVal = date.toLocaleDateString() + ' ' + date.toLocaleTimeString();
2958
+ retVal = date.toLocaleDateString() + " " + date.toLocaleTimeString();
2631
2959
  break;
2632
2960
  default:
2633
- retVal = $scope.dataEventFunctions[schemaElm['params']](record);
2961
+ retVal = $scope.dataEventFunctions[schemaElm["params"]](record);
2634
2962
  }
2635
2963
  }
2636
2964
  }
@@ -2647,7 +2975,7 @@ var fng;
2647
2975
  if (angular.isArray(theValue)) {
2648
2976
  for (var i = theValue.length - 1; i >= 0; i--) {
2649
2977
  var type = typeof theValue[i];
2650
- if (type === 'undefined' || (type === 'object' && Object.keys(theValue[i]).length === 0)) {
2978
+ if (type === "undefined" || (type === "object" && Object.keys(theValue[i]).length === 0)) {
2651
2979
  theValue.splice(i, 1);
2652
2980
  }
2653
2981
  }
@@ -2669,8 +2997,8 @@ var fng;
2669
2997
  }
2670
2998
  // Set up the lookup lists (value and id) on the scope for an internal lookup. Called by convertToAngularModel and $watch
2671
2999
  function setUpInternalLookupLists($scope, options, ids, newVal, valueAttrib) {
2672
- var optionsArray = (typeof options === 'string' ? $scope[options] : options);
2673
- var idsArray = (typeof ids === 'string' ? $scope[ids] : ids);
3000
+ var optionsArray = (typeof options === "string" ? $scope[options] : options);
3001
+ var idsArray = (typeof ids === "string" ? $scope[ids] : ids);
2674
3002
  optionsArray.length = 0;
2675
3003
  idsArray.length = 0;
2676
3004
  if (!!newVal && (newVal.length > 0)) {
@@ -2692,10 +3020,10 @@ var fng;
2692
3020
  result = true;
2693
3021
  }
2694
3022
  else if (!aSchema.directive) {
2695
- if (aSchema.type === 'text') {
3023
+ if (aSchema.type === "text") {
2696
3024
  result = true;
2697
3025
  }
2698
- else if (aSchema.type === 'select' && !aSchema.ids) {
3026
+ else if (aSchema.type === "select" && !aSchema.ids) {
2699
3027
  result = true;
2700
3028
  }
2701
3029
  }
@@ -2705,7 +3033,7 @@ var fng;
2705
3033
  function getConversionObject(scope, entryName, schemaName) {
2706
3034
  var conversions = scope.conversions;
2707
3035
  if (schemaName) {
2708
- conversions = conversions[schemaName] || {};
3036
+ conversions = getData(conversions, schemaName) || {};
2709
3037
  }
2710
3038
  return conversions[entryName];
2711
3039
  }
@@ -2716,37 +3044,58 @@ var fng;
2716
3044
  for (var i = 0; i < schema.length; i++) {
2717
3045
  var schemaEntry = schema[i];
2718
3046
  var fieldName = schemaEntry.name.slice(prefixLength);
3047
+ if (!fieldName.length) {
3048
+ fieldName = schemaEntry.name.split('.').pop();
3049
+ }
2719
3050
  var fieldValue = getData(anObject, fieldName);
3051
+ if (schemaEntry.intType === 'date' && typeof fieldValue === 'string') {
3052
+ setData(anObject, fieldName, null, new Date(fieldValue));
3053
+ }
2720
3054
  if (schemaEntry.schema) {
2721
3055
  if (fieldValue) {
2722
3056
  for (var j = 0; j < fieldValue.length; j++) {
2723
- fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j], prefixLength + 1 + fieldName.length, $scope, fieldName, master, j);
3057
+ fieldValue[j] = convertToAngularModel(schemaEntry.schema, fieldValue[j], 1 + fieldName.length, $scope, fieldName, master, j);
2724
3058
  }
2725
3059
  }
2726
3060
  }
2727
3061
  else {
2728
- if (schemaEntry.ref && schemaEntry.ref.type === 'internal') {
2729
- setUpInternalLookupLists($scope, schemaEntry.options, schemaEntry.ids, master[schemaEntry.ref.property], schemaEntry.ref.value);
3062
+ if (schemaEntry.internalRef) {
3063
+ setUpInternalLookupLists($scope, schemaEntry.options, schemaEntry.ids, master[schemaEntry.internalRef.property], schemaEntry.internalRef.value);
2730
3064
  }
2731
3065
  // Convert {array:['item 1']} to {array:[{x:'item 1'}]}
2732
- var thisField = getListData($scope, anObject, fieldName);
2733
- if (schemaEntry.array && simpleArrayNeedsX(schemaEntry) && thisField) {
3066
+ var thisField = getListData(anObject, fieldName, null, $scope);
3067
+ if (schemaEntry.array &&
3068
+ simpleArrayNeedsX(schemaEntry) &&
3069
+ thisField &&
3070
+ !(thisField.length > 0 && thisField[0].x) // Don't keep on coverting
3071
+ ) {
2734
3072
  for (var k = 0; k < thisField.length; k++) {
2735
3073
  thisField[k] = { x: thisField[k] };
2736
3074
  }
2737
3075
  }
2738
3076
  // Convert {lookup:'012abcde'} to {lookup:'List description for 012abcde'}
2739
- var idList = $scope[suffixCleanId(schemaEntry, '_ids')];
3077
+ var idList = $scope[suffixCleanId(schemaEntry, "_ids")];
2740
3078
  var thisConversion = void 0;
2741
3079
  if (fieldValue && idList && idList.length > 0) {
2742
- if (fieldName.indexOf('.') !== -1) {
2743
- throw new Error('Trying to directly assign to a nested field 332');
3080
+ if (fieldName.indexOf(".") !== -1) {
3081
+ throw new Error("Trying to directly assign to a nested field 332");
2744
3082
  } // Not sure that this can happen, but put in a runtime test
2745
- anObject[fieldName] = convertForeignKeys(schemaEntry, fieldValue, $scope[suffixCleanId(schemaEntry, 'Options')], idList);
3083
+ if (
3084
+ /*
3085
+ Check we are starting with an ObjectId (ie not being called because of $watch on conversion, with a
3086
+ converted value, which would cause an exception)
3087
+ */
3088
+ fieldValue.toString().match(/^[a-f0-9]{24}$/) &&
3089
+ /*
3090
+ We are not suppressing conversions
3091
+ */
3092
+ (!schemaEntry.internalRef || !schemaEntry.internalRef.noConvert)) {
3093
+ anObject[fieldName] = convertForeignKeys(schemaEntry, fieldValue, $scope[suffixCleanId(schemaEntry, "Options")], idList);
3094
+ }
2746
3095
  }
2747
3096
  else if (schemaEntry.select2) {
2748
3097
  // Do nothing with these - handled elsewhere (and deprecated)
2749
- console.log('fng-select2 is deprecated - use fng-ui-select instead');
3098
+ console.log("fng-select2 is deprecated - use fng-ui-select instead");
2750
3099
  void (schemaEntry.select2);
2751
3100
  }
2752
3101
  else if (fieldValue && (thisConversion = getConversionObject($scope, fieldName, schemaName)) &&
@@ -2755,7 +3104,7 @@ var fng;
2755
3104
  thisConversion.fngajax(fieldValue, schemaEntry, function (updateEntry, value) {
2756
3105
  // Update the master and (preserving pristine if appropriate) the record
2757
3106
  setData(master, updateEntry.name, offset, value);
2758
- preservePristine(angular.element('#' + updateEntry.id), function () {
3107
+ preservePristine(angular.element("#" + updateEntry.id), function () {
2759
3108
  setData($scope.record, updateEntry.name, offset, value);
2760
3109
  });
2761
3110
  });
@@ -2768,9 +3117,9 @@ var fng;
2768
3117
  // Called when the model is read and when the lookups are read
2769
3118
  // No support for nested schemas here as it is called from convertToAngularModel which does that
2770
3119
  function convertForeignKeys(schemaElement, input, values, ids) {
2771
- if (schemaElement.array) {
3120
+ if (schemaElement.array || angular.isArray(input)) {
2772
3121
  var returnArray = [];
2773
- var needsX = !schemaElement.directive || simpleArrayNeedsX(schemaElement);
3122
+ var needsX = schemaElement.array && (!schemaElement.directive || simpleArrayNeedsX(schemaElement));
2774
3123
  for (var j = 0; j < input.length; j++) {
2775
3124
  var val = input[j];
2776
3125
  if (val && val.x) {
@@ -2814,7 +3163,7 @@ var fng;
2814
3163
  else {
2815
3164
  var index = valuesArray.indexOf(textToConvert);
2816
3165
  if (index === -1) {
2817
- throw new Error('convertListValueToId: Invalid data - value ' + textToConvert + ' not found in ' + valuesArray + ' processing ' + fname);
3166
+ throw new Error("convertListValueToId: Invalid data - value " + textToConvert + " not found in " + valuesArray + " processing " + fname);
2818
3167
  }
2819
3168
  return idsArray[index];
2820
3169
  }
@@ -2822,7 +3171,7 @@ var fng;
2822
3171
  var preservePristine = function preservePristine(element, fn) {
2823
3172
  // stop the form being set to dirty when a fn is called
2824
3173
  // Use when the record (and master) need to be updated by lookup values displayed asynchronously
2825
- var modelController = element.inheritedData('$ngModelController');
3174
+ var modelController = element.inheritedData("$ngModelController");
2826
3175
  var isClean = (modelController && modelController.$pristine);
2827
3176
  if (isClean) {
2828
3177
  // fake it to dirty here and reset after call to fn
@@ -2834,18 +3183,21 @@ var fng;
2834
3183
  }
2835
3184
  };
2836
3185
  var convertIdToListValue = function convertIdToListValue(id, idsArray, valuesArray, fname) {
2837
- if (typeof (id) === 'object') {
3186
+ if (typeof (id) === "object") {
2838
3187
  id = id.id;
2839
3188
  }
2840
3189
  var index = idsArray.indexOf(id);
2841
3190
  if (index === -1) {
2842
- throw new Error('convertIdToListValue: Invalid data - id ' + id + ' not found in ' + idsArray + ' processing ' + fname);
3191
+ index = valuesArray.indexOf(id); // This can get called twice - second time with converted value (not sure how atm) so protect against that...
3192
+ if (index === -1) {
3193
+ throw new Error("convertIdToListValue: Invalid data - id " + id + " not found in " + idsArray + " processing " + fname);
3194
+ }
2843
3195
  }
2844
3196
  return valuesArray[index];
2845
3197
  };
2846
3198
  var processServerData = function processServerData(recordFromServer, $scope, ctrlState) {
2847
3199
  ctrlState.master = convertToAngularModel($scope.formSchema, recordFromServer, 0, $scope);
2848
- $scope.phase = 'ready';
3200
+ $scope.phase = "ready";
2849
3201
  $scope.cancel();
2850
3202
  };
2851
3203
  function convertOldToNew(ref, val, attrib, newVals, oldVals) {
@@ -2864,102 +3216,123 @@ var fng;
2864
3216
  var listOnly = (!$scope.id && !$scope.newRecord);
2865
3217
  // passing null for formSchema parameter prevents all the work being done when we are just after the list data,
2866
3218
  // but should be removed when/if formschemas are cached
2867
- formGeneratorInstance.handleSchema('Main ' + $scope.modelName, schema, listOnly ? null : $scope.formSchema, $scope.listSchema, '', true, $scope, ctrlState);
3219
+ formGeneratorInstance.handleSchema("Main " + $scope.modelName, schema, listOnly ? null : $scope.formSchema, $scope.listSchema, "", true, $scope, ctrlState);
3220
+ function processLookupHandlers(newValue, oldValue) {
3221
+ // If we have any internal lookups then update the references
3222
+ $scope.internalLookups.forEach(function (lkp) {
3223
+ var newVal = newValue[lkp.ref.property];
3224
+ var oldVal = oldValue[lkp.ref.property];
3225
+ setUpInternalLookupLists($scope, lkp.lookupOptions, lkp.lookupIds, newVal, lkp.ref.value);
3226
+ // now change the looked-up values that matched the old to the new
3227
+ if ((newVal && newVal.length > 0) || (oldVal && oldVal.length > 0)) {
3228
+ lkp.handlers.forEach(function (h) {
3229
+ if (h.possibleArray) {
3230
+ var arr = getData($scope.record, h.possibleArray, null);
3231
+ if (arr && arr.length > 0) {
3232
+ arr.forEach(function (a) { return convertOldToNew(lkp.ref, a, h.lastPart, newVal, oldVal); });
3233
+ }
3234
+ }
3235
+ else if (angular.isArray($scope.record[h.lastPart])) {
3236
+ $scope.record[h.lastPart].forEach(function (a) {
3237
+ convertOldToNew(lkp.ref, a, "x", newVal, oldVal);
3238
+ });
3239
+ }
3240
+ else {
3241
+ convertOldToNew(lkp.ref, $scope.record, h.lastPart, newVal, oldVal);
3242
+ }
3243
+ });
3244
+ }
3245
+ });
3246
+ // If we have any list lookups then update the references
3247
+ $scope.listLookups.forEach(function (lkp) {
3248
+ function extractIdVal(obj, idString) {
3249
+ var retVal = obj[idString];
3250
+ if (retVal && retVal.id) {
3251
+ retVal = retVal.id;
3252
+ }
3253
+ return retVal;
3254
+ }
3255
+ function blankListLookup(inst) {
3256
+ setData($scope.record, inst.name);
3257
+ }
3258
+ var idString = lkp.ref.id.slice(1);
3259
+ if (idString.includes(".")) {
3260
+ throw new Error("No support for nested list lookups yet - ".concat(JSON.stringify(lkp.ref)));
3261
+ }
3262
+ var newVal = extractIdVal(newValue, idString);
3263
+ var oldVal = extractIdVal(oldValue, idString);
3264
+ if (newVal !== oldVal) {
3265
+ lkp.handlers.forEach(function (h) {
3266
+ $scope[h.formInstructions.options].length = 0;
3267
+ $scope[h.formInstructions.ids].length = 0;
3268
+ });
3269
+ if (newVal) {
3270
+ SubmissionsService.readRecord(lkp.ref.collection, newVal).then(function (response) {
3271
+ lkp.handlers.forEach(function (h) {
3272
+ var optionsList = $scope[h.formInstructions.options];
3273
+ var idList = $scope[h.formInstructions.ids];
3274
+ var data = response.data[lkp.ref.property] || [];
3275
+ for (var i = 0; i < data.length; i++) {
3276
+ var option = data[i][lkp.ref.value];
3277
+ var pos = _.sortedIndex(optionsList, option);
3278
+ // handle dupes
3279
+ if (optionsList[pos] === option) {
3280
+ option = option + " (" + data[i]._id + ")";
3281
+ pos = _.sortedIndex(optionsList, option);
3282
+ }
3283
+ optionsList.splice(pos, 0, option);
3284
+ idList.splice(pos, 0, data[i]._id);
3285
+ }
3286
+ if (Object.keys(oldValue).length === 0) {
3287
+ // Not sure how safe this is, but the record is fresh so I think it's OK...
3288
+ updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState, true);
3289
+ }
3290
+ else {
3291
+ // Here we are reacting to a change in the lookup pointer in the record.
3292
+ // We need to blank our lookup field as it will not exist
3293
+ blankListLookup(h.formInstructions);
3294
+ }
3295
+ });
3296
+ });
3297
+ }
3298
+ else {
3299
+ lkp.handlers.forEach(function (h) {
3300
+ blankListLookup(h.formInstructions);
3301
+ });
3302
+ }
3303
+ }
3304
+ });
3305
+ }
3306
+ function notifyReady() {
3307
+ $scope.phase = "ready";
3308
+ $scope.cancel();
3309
+ processLookupHandlers($scope.record, {});
3310
+ }
2868
3311
  if (listOnly) {
2869
3312
  ctrlState.allowLocationChange = true;
2870
3313
  }
2871
3314
  else {
2872
3315
  var force = true;
2873
3316
  if (!$scope.newRecord) {
2874
- $scope.dropConversionWatcher = $scope.$watchCollection('conversions', function (newValue, oldValue) {
3317
+ $scope.dropConversionWatcher = $scope.$watchCollection("conversions", function (newValue, oldValue) {
2875
3318
  if (newValue !== oldValue && $scope.originalData) {
2876
3319
  processServerData($scope.originalData, $scope, ctrlState);
2877
3320
  }
2878
3321
  });
2879
3322
  }
2880
- $scope.$watch('record', function (newValue, oldValue) {
3323
+ $scope.$watch("record", function (newValue, oldValue) {
2881
3324
  if (newValue !== oldValue) {
2882
3325
  if (Object.keys(oldValue).length > 0 && $scope.dropConversionWatcher) {
2883
3326
  $scope.dropConversionWatcher(); // Don't want to convert changed data
2884
3327
  $scope.dropConversionWatcher = null;
2885
3328
  }
2886
3329
  force = formGeneratorInstance.updateDataDependentDisplay(newValue, oldValue, force, $scope);
2887
- // If we have any internal lookups then update the references
2888
- $scope.internalLookups.forEach(function (lkp) {
2889
- var newVal = newValue[lkp.ref.property];
2890
- var oldVal = oldValue[lkp.ref.property];
2891
- setUpInternalLookupLists($scope, lkp.lookupOptions, lkp.lookupIds, newVal, lkp.ref.value);
2892
- // now change the looked-up values that matched the old to the new
2893
- if ((newVal && newVal.length > 0) || (oldVal && oldVal.length > 0)) {
2894
- lkp.handlers.forEach(function (h) {
2895
- if (h.possibleArray) {
2896
- var arr = getData($scope.record, h.possibleArray, null);
2897
- if (arr && arr.length > 0) {
2898
- arr.forEach(function (a) { return convertOldToNew(lkp.ref, a, h.lastPart, newVal, oldVal); });
2899
- }
2900
- }
2901
- else if (angular.isArray($scope.record[h.lastPart])) {
2902
- $scope.record[h.lastPart].forEach(function (a) {
2903
- convertOldToNew(lkp.ref, a, 'x', newVal, oldVal);
2904
- });
2905
- }
2906
- else {
2907
- convertOldToNew(lkp.ref, $scope.record, h.lastPart, newVal, oldVal);
2908
- }
2909
- });
2910
- }
2911
- });
2912
- // If we have any list lookups then update the references
2913
- $scope.listLookups.forEach(function (lkp) {
2914
- function extractIdVal(obj, idString) {
2915
- var retVal = obj[idString];
2916
- if (retVal && retVal.id) {
2917
- retVal = retVal.id;
2918
- }
2919
- return retVal;
2920
- }
2921
- var idString = lkp.ref.id.slice(1);
2922
- if (idString.includes('.')) {
2923
- throw new Error("No support for nested list lookups yet - " + JSON.stringify(lkp.ref));
2924
- }
2925
- var newVal = extractIdVal(newValue, idString);
2926
- var oldVal = extractIdVal(oldValue, idString);
2927
- if (newVal !== oldVal) {
2928
- if (newVal) {
2929
- SubmissionsService.readRecord(lkp.ref.collection, newVal).then(function (response) {
2930
- lkp.handlers.forEach(function (h) {
2931
- var optionsList = $scope[h.formInstructions.options];
2932
- var idList = $scope[h.formInstructions.ids];
2933
- var data = response.data[lkp.ref.property];
2934
- for (var i = 0; i < data.length; i++) {
2935
- var option = data[i][lkp.ref.value];
2936
- var pos = _.sortedIndex(optionsList, option);
2937
- // handle dupes
2938
- if (optionsList[pos] === option) {
2939
- option = option + ' (' + data[i]._id + ')';
2940
- pos = _.sortedIndex(optionsList, option);
2941
- }
2942
- optionsList.splice(pos, 0, option);
2943
- idList.splice(pos, 0, data[i]._id);
2944
- }
2945
- updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState);
2946
- });
2947
- });
2948
- }
2949
- else {
2950
- lkp.handlers.forEach(function (h) {
2951
- $scope[h.formInstructions.options].length = 0;
2952
- $scope[h.formInstructions.ids].length = 0;
2953
- updateRecordWithLookupValues(h.formInstructions, $scope, ctrlState);
2954
- });
2955
- }
2956
- }
2957
- });
3330
+ processLookupHandlers(newValue, oldValue);
2958
3331
  }
2959
3332
  }, true);
2960
3333
  if ($scope.id) {
2961
3334
  // Going to read a record
2962
- if (typeof $scope.dataEventFunctions.onBeforeRead === 'function') {
3335
+ if (typeof $scope.dataEventFunctions.onBeforeRead === "function") {
2963
3336
  $scope.dataEventFunctions.onBeforeRead($scope.id, function (err) {
2964
3337
  if (err) {
2965
3338
  $scope.showError(err);
@@ -2975,99 +3348,125 @@ var fng;
2975
3348
  }
2976
3349
  else {
2977
3350
  // New record
2978
- ctrlState.master = {};
3351
+ ctrlState.allowLocationChange = false;
3352
+ ctrlState.master = $scope.setDefaults($scope.formSchema);
2979
3353
  var passedRecord = $scope.initialiseNewRecord || $location.$$search.r;
2980
3354
  if (passedRecord) {
2981
3355
  try {
2982
- ctrlState.master = JSON.parse(passedRecord);
2983
- // Although this is a new record we are making it dirty from the url so we need to $setDirty
2984
- $scope.$on('fngCancel', function () {
2985
- setTimeout(function () {
2986
- if ($scope[$scope.topLevelFormName]) {
2987
- $scope[$scope.topLevelFormName].$setDirty();
2988
- }
2989
- }, 2); // Has to fire after the setPristime timeout.
2990
- });
3356
+ Object.assign(ctrlState.master, JSON.parse(passedRecord));
3357
+ if (!$scope["newRecordsStartPristine"]) {
3358
+ // Although this is a new record we are making it dirty from the url so we need to $setDirty
3359
+ $scope.$on("fngCancel", function () {
3360
+ $timeout(function () {
3361
+ if ($scope[$scope.topLevelFormName]) {
3362
+ $scope[$scope.topLevelFormName].$setDirty();
3363
+ }
3364
+ }, 1000); // Has to fire after the setPristime timeout.
3365
+ });
3366
+ }
2991
3367
  }
2992
3368
  catch (e) {
2993
- console.log('Error parsing specified record : ' + e.message);
3369
+ console.log("Error parsing specified record : " + e.message);
2994
3370
  }
2995
3371
  }
2996
- if (typeof $scope.dataEventFunctions.onInitialiseNewRecord === 'function') {
3372
+ if (typeof $scope.dataEventFunctions.onInitialiseNewRecord === "function") {
3373
+ console.log("onInitialiseNewRecord is deprecated - use the async version - onNewRecordInit(data,cb)");
2997
3374
  $scope.dataEventFunctions.onInitialiseNewRecord(ctrlState.master);
2998
3375
  }
2999
- $scope.phase = 'ready';
3000
- $scope.cancel();
3376
+ if (typeof $scope.dataEventFunctions.onNewRecordInit === "function") {
3377
+ $scope.dataEventFunctions.onNewRecordInit(ctrlState.master, function (err) {
3378
+ if (err) {
3379
+ $scope.showError(err);
3380
+ }
3381
+ else {
3382
+ notifyReady();
3383
+ }
3384
+ });
3385
+ }
3386
+ else {
3387
+ notifyReady();
3388
+ }
3001
3389
  }
3002
3390
  }
3003
3391
  }
3004
3392
  function handleError($scope) {
3005
3393
  return function (response) {
3006
3394
  if ([200, 400].indexOf(response.status) !== -1) {
3007
- var errorMessage = '';
3395
+ var errorMessage = "";
3008
3396
  for (var errorField in response.data.errors) {
3009
3397
  if (response.data.errors.hasOwnProperty(errorField)) {
3010
- errorMessage += '<li><b>' + $filter('titleCase')(errorField) + ': </b> ';
3398
+ errorMessage += "<li><b>" + $filter("titleCase")(errorField) + ": </b> ";
3011
3399
  switch (response.data.errors[errorField].type) {
3012
- case 'enum':
3013
- errorMessage += 'You need to select from the list of values';
3400
+ case "enum":
3401
+ errorMessage += "You need to select from the list of values";
3014
3402
  break;
3015
3403
  default:
3016
3404
  errorMessage += response.data.errors[errorField].message;
3017
3405
  break;
3018
3406
  }
3019
- errorMessage += '</li>';
3407
+ errorMessage += "</li>";
3020
3408
  }
3021
3409
  }
3022
3410
  if (errorMessage.length > 0) {
3023
- errorMessage = response.data.message + '<br /><ul>' + errorMessage + '</ul>';
3411
+ errorMessage = (response.data.message || response.data._message) + "<br /><ul>" + errorMessage + "</ul>";
3024
3412
  }
3025
3413
  else {
3026
- errorMessage = response.data.message || 'Error! Sorry - No further details available.';
3414
+ errorMessage = response.data.message || response.data._message || response.data.err || "Error! Sorry - No further details available.";
3027
3415
  }
3028
3416
  $scope.showError(errorMessage);
3029
3417
  }
3030
3418
  else {
3031
- $scope.showError(response.status + ' ' + JSON.stringify(response.data));
3419
+ $scope.showError(response.status + " " + JSON.stringify(response.data));
3032
3420
  }
3033
3421
  };
3034
3422
  }
3035
3423
  function handleIncomingData(data, $scope, ctrlState) {
3036
3424
  ctrlState.allowLocationChange = false;
3037
- $scope.phase = 'reading';
3038
- if (typeof $scope.dataEventFunctions.onAfterRead === 'function') {
3425
+ $scope.phase = "reading";
3426
+ if (typeof $scope.dataEventFunctions.onAfterRead === "function") {
3039
3427
  $scope.dataEventFunctions.onAfterRead(data);
3040
3428
  }
3041
3429
  $scope.originalData = data;
3042
3430
  processServerData(data, $scope, ctrlState);
3043
3431
  }
3432
+ function addArrayLookupToLookupList($scope, formInstructions, ref, lookups) {
3433
+ var nameElements = formInstructions.name.split(".");
3434
+ var refHandler = lookups.find(function (lkp) {
3435
+ return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
3436
+ });
3437
+ var thisHandler = {
3438
+ formInstructions: formInstructions,
3439
+ lastPart: nameElements.pop(),
3440
+ possibleArray: nameElements.join(".")
3441
+ };
3442
+ if (!refHandler) {
3443
+ refHandler = {
3444
+ ref: ref,
3445
+ lookupOptions: [],
3446
+ lookupIds: [],
3447
+ handlers: []
3448
+ };
3449
+ lookups.push(refHandler);
3450
+ }
3451
+ refHandler.handlers.push(thisHandler);
3452
+ $scope[formInstructions.options] = refHandler.lookupOptions;
3453
+ $scope[formInstructions.ids] = refHandler.lookupIds;
3454
+ }
3044
3455
  return {
3045
3456
  readRecord: function readRecord($scope, ctrlState) {
3046
- // TODO Consider using $parse for this - http://bahmutov.calepin.co/angularjs-parse-hacks.html
3047
- SubmissionsService.readRecord($scope.modelName, $scope.id)
3457
+ $scope.readingRecord = SubmissionsService.readRecord($scope.modelName, $scope.id);
3458
+ $scope.readingRecord
3048
3459
  .then(function (response) {
3049
- var data = response.data;
3050
- if (data.success === false) {
3051
- $location.path('/404');
3052
- // TODO Figure out tab history updates (check for other tab-history-todos)
3053
- // } else if (response.master) {
3054
- //
3055
- // ctrlState.allowLocationChange = false;
3056
- // $scope.phase = 'ready';
3057
- // $scope.record = angular.copy(response.data);
3058
- // ctrlState.master = angular.copy(response.master);
3059
- // if (response.changed) {
3060
- // $timeout(() => {
3061
- // $scope[$scope.topLevelFormName].$setDirty();
3062
- // });
3063
- // } else {
3064
- // $timeout($scope.setPristine);
3065
- // }
3460
+ var data = angular.copy(response.data);
3461
+ handleIncomingData(data, $scope, ctrlState);
3462
+ }, function (error) {
3463
+ if (error.status === 404) {
3464
+ $location.path("/404");
3066
3465
  }
3067
3466
  else {
3068
- handleIncomingData(data, $scope, ctrlState);
3467
+ $scope.handleHttpError(error);
3069
3468
  }
3070
- }, $scope.handleHttpError);
3469
+ });
3071
3470
  },
3072
3471
  scrollTheList: function scrollTheList($scope) {
3073
3472
  var pagesLoaded = $scope.pagesLoaded;
@@ -3087,31 +3486,38 @@ var fng;
3087
3486
  $scope.recordList = $scope.recordList.concat(data);
3088
3487
  }
3089
3488
  else {
3090
- console.log('DEBUG: infinite scroll component asked for a page twice');
3489
+ console.log("DEBUG: infinite scroll component asked for a page twice - the model was " + $scope.modelName);
3091
3490
  }
3092
3491
  }
3093
3492
  else {
3094
- $scope.showError(data, 'Invalid query');
3493
+ $scope.showError(data, "Invalid query");
3095
3494
  }
3096
3495
  }, $scope.handleHttpError);
3097
3496
  },
3098
- // TODO: Do we need model here? Can we not infer it from scope?
3099
- deleteRecord: function deleteRecord(model, id, $scope, ctrlState) {
3100
- SubmissionsService.deleteRecord(model, id)
3497
+ deleteRecord: function deleteRecord(id, $scope, ctrlState) {
3498
+ SubmissionsService.deleteRecord($scope.modelName, id)
3101
3499
  .then(function () {
3102
- if (typeof $scope.dataEventFunctions.onAfterDelete === 'function') {
3500
+ if (typeof $scope.dataEventFunctions.onAfterDelete === "function") {
3103
3501
  $scope.dataEventFunctions.onAfterDelete(ctrlState.master);
3104
3502
  }
3105
- routingService.redirectTo()('list', $scope, $location);
3503
+ routingService.redirectTo()("onDelete", $scope, $location);
3504
+ }, function (err) {
3505
+ if (err.status === 404) {
3506
+ // Someone already deleted it
3507
+ routingService.redirectTo()("onDelete", $scope, $location);
3508
+ }
3509
+ else {
3510
+ $scope.showError("".concat(err.statusText, " (").concat(err.status, ") while deleting record<br />").concat(err.data), 'Error deleting record');
3511
+ }
3106
3512
  });
3107
3513
  },
3108
3514
  updateDocument: function updateDocument(dataToSave, options, $scope, ctrlState) {
3109
- $scope.phase = 'updating';
3515
+ $scope.phase = "updating";
3110
3516
  SubmissionsService.updateRecord($scope.modelName, $scope.id, dataToSave)
3111
3517
  .then(function (response) {
3112
3518
  var data = response.data;
3113
3519
  if (data.success !== false) {
3114
- if (typeof $scope.dataEventFunctions.onAfterUpdate === 'function') {
3520
+ if (typeof $scope.dataEventFunctions.onAfterUpdate === "function") {
3115
3521
  $scope.dataEventFunctions.onAfterUpdate(data, ctrlState.master);
3116
3522
  }
3117
3523
  if (options.redirect) {
@@ -3130,19 +3536,20 @@ var fng;
3130
3536
  }
3131
3537
  }, $scope.handleHttpError);
3132
3538
  },
3133
- createNew: function createNew(dataToSave, options, $scope) {
3539
+ createNew: function createNew(dataToSave, options, $scope, ctrlState) {
3134
3540
  SubmissionsService.createRecord($scope.modelName, dataToSave)
3135
3541
  .then(function (response) {
3136
3542
  var data = response.data;
3137
3543
  if (data.success !== false) {
3138
- if (typeof $scope.dataEventFunctions.onAfterCreate === 'function') {
3544
+ ctrlState.allowLocationChange = true;
3545
+ if (typeof $scope.dataEventFunctions.onAfterCreate === "function") {
3139
3546
  $scope.dataEventFunctions.onAfterCreate(data);
3140
3547
  }
3141
3548
  if (options.redirect) {
3142
3549
  $window.location = options.redirect;
3143
3550
  }
3144
3551
  else {
3145
- routingService.redirectTo()('edit', $scope, $location, data._id);
3552
+ routingService.redirectTo()("edit", $scope, $location, data._id);
3146
3553
  }
3147
3554
  }
3148
3555
  else {
@@ -3160,9 +3567,9 @@ var fng;
3160
3567
  .then(function (response) {
3161
3568
  var data = response.data;
3162
3569
  var listInstructions = [];
3163
- handleSchema('Lookup ' + lookupCollection, data, null, listInstructions, '', false, $scope, ctrlState);
3570
+ handleSchema("Lookup " + lookupCollection, data, null, listInstructions, "", false, $scope, ctrlState);
3164
3571
  var dataRequest;
3165
- if (typeof schemaElement.filter !== 'undefined' && schemaElement.filter) {
3572
+ if (typeof schemaElement.filter !== "undefined" && schemaElement.filter) {
3166
3573
  dataRequest = SubmissionsService.getPagedAndFilteredList(lookupCollection, schemaElement.filter);
3167
3574
  }
3168
3575
  else {
@@ -3170,25 +3577,30 @@ var fng;
3170
3577
  }
3171
3578
  dataRequest
3172
3579
  .then(function (response) {
3173
- var data = response.data;
3580
+ var data = angular.copy(response.data);
3174
3581
  if (data) {
3175
3582
  for (var i = 0; i < data.length; i++) {
3176
- var option = '';
3583
+ var option = "";
3177
3584
  for (var j = 0; j < listInstructions.length; j++) {
3178
3585
  var thisVal = data[i][listInstructions[j].name];
3179
- option += thisVal ? thisVal + ' ' : '';
3586
+ option += thisVal ? thisVal + " " : "";
3180
3587
  }
3181
3588
  option = option.trim();
3182
3589
  var pos = _.sortedIndex(optionsList, option);
3183
3590
  // handle dupes (ideally people will use unique indexes to stop them but...)
3184
3591
  if (optionsList[pos] === option) {
3185
- option = option + ' (' + data[i]._id + ')';
3592
+ option = option + " (" + data[i]._id + ")";
3186
3593
  pos = _.sortedIndex(optionsList, option);
3187
3594
  }
3188
3595
  optionsList.splice(pos, 0, option);
3189
3596
  idList.splice(pos, 0, data[i]._id);
3190
3597
  }
3191
- updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
3598
+ if ($scope.readingRecord) {
3599
+ $scope.readingRecord
3600
+ .then(function () {
3601
+ updateRecordWithLookupValues(schemaElement, $scope, ctrlState);
3602
+ });
3603
+ }
3192
3604
  }
3193
3605
  });
3194
3606
  });
@@ -3196,31 +3608,9 @@ var fng;
3196
3608
  setUpLookupListOptions: function setUpLookupListOptions(ref, formInstructions, $scope, ctrlState) {
3197
3609
  var optionsList = $scope[formInstructions.options] = [];
3198
3610
  var idList = $scope[formInstructions.ids] = [];
3199
- if (ref.id[0] === '$') {
3200
- // id of document we are doing lookup from comes from record, so we need to deal with in $watch
3201
- // by adding it to listLookups
3202
- var nameElements = formInstructions.name.split('.');
3203
- var refHandler = $scope.listLookups.find(function (lkp) {
3204
- return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
3205
- });
3206
- var thisHandler = {
3207
- formInstructions: formInstructions,
3208
- lastPart: nameElements.pop(),
3209
- possibleArray: nameElements.join('.')
3210
- };
3211
- if (!refHandler) {
3212
- refHandler = {
3213
- ref: ref,
3214
- lookupOptions: [],
3215
- lookupIds: [],
3216
- handlers: []
3217
- };
3218
- $scope.listLookups.push(refHandler);
3219
- }
3220
- refHandler.handlers.push(thisHandler);
3221
- $scope[formInstructions.options] = refHandler.lookupOptions;
3222
- $scope[formInstructions.ids] = refHandler.lookupIds;
3223
- // TODO DRY this and handleInternalLookup below
3611
+ if (ref.id[0] === "$") {
3612
+ // id of document that contains out lookup list comes from record, so we need to deal with in $watch by adding it to listLookups
3613
+ addArrayLookupToLookupList($scope, formInstructions, ref, $scope.listLookups);
3224
3614
  }
3225
3615
  else {
3226
3616
  // we can do it now
@@ -3231,7 +3621,7 @@ var fng;
3231
3621
  var pos = _.sortedIndex(optionsList, option);
3232
3622
  // handle dupes
3233
3623
  if (optionsList[pos] === option) {
3234
- option = option + ' (' + data[i]._id + ')';
3624
+ option = option + " (" + data[i]._id + ")";
3235
3625
  pos = _.sortedIndex(optionsList, option);
3236
3626
  }
3237
3627
  optionsList.splice(pos, 0, option);
@@ -3242,27 +3632,7 @@ var fng;
3242
3632
  }
3243
3633
  },
3244
3634
  handleInternalLookup: function handleInternalLookup($scope, formInstructions, ref) {
3245
- var nameElements = formInstructions.name.split('.');
3246
- var refHandler = $scope.internalLookups.find(function (lkp) {
3247
- return lkp.ref.property === ref.property && lkp.ref.value === ref.value;
3248
- });
3249
- var thisHandler = {
3250
- formInstructions: formInstructions,
3251
- lastPart: nameElements.pop(),
3252
- possibleArray: nameElements.join('.')
3253
- };
3254
- if (!refHandler) {
3255
- refHandler = {
3256
- ref: ref,
3257
- lookupOptions: [],
3258
- lookupIds: [],
3259
- handlers: []
3260
- };
3261
- $scope.internalLookups.push(refHandler);
3262
- }
3263
- refHandler.handlers.push(thisHandler);
3264
- $scope[formInstructions.options] = refHandler.lookupOptions;
3265
- $scope[formInstructions.ids] = refHandler.lookupIds;
3635
+ addArrayLookupToLookupList($scope, formInstructions, ref, $scope.internalLookups);
3266
3636
  },
3267
3637
  preservePristine: preservePristine,
3268
3638
  // Reverse the process of convertToAngularModel
@@ -3279,48 +3649,54 @@ var fng;
3279
3649
  }
3280
3650
  return retVal;
3281
3651
  }
3282
- for (var i = 0; i < schema.length; i++) {
3283
- var fieldname = schema[i].name.slice(prefixLength);
3284
- var thisField = getListData($scope, anObject, fieldname);
3285
- if (schema[i].schema) {
3652
+ var _loop_1 = function () {
3653
+ var schemaI = schema[i];
3654
+ var fieldname = schemaI.name.slice(prefixLength);
3655
+ var thisField = getListData(anObject, fieldname, null, $scope);
3656
+ if (schemaI.schema) {
3286
3657
  if (thisField) {
3287
3658
  for (var j = 0; j < thisField.length; j++) {
3288
- thisField[j] = convertToMongoModel(schema[i].schema, thisField[j], prefixLength + 1 + fieldname.length, $scope, fieldname);
3659
+ thisField[j] = convertToMongoModel(schemaI.schema, thisField[j], 1 + fieldname.length, $scope, fieldname);
3289
3660
  }
3290
3661
  }
3291
3662
  }
3292
3663
  else {
3293
3664
  // Convert {array:[{x:'item 1'}]} to {array:['item 1']}
3294
- if (schema[i].array && simpleArrayNeedsX(schema[i]) && thisField) {
3665
+ if (schemaI.array && simpleArrayNeedsX(schemaI) && thisField) {
3295
3666
  for (var k = 0; k < thisField.length; k++) {
3296
3667
  thisField[k] = thisField[k].x;
3297
3668
  }
3298
3669
  }
3299
3670
  // Convert {lookup:'List description for 012abcde'} to {lookup:'012abcde'}
3300
- var idList = $scope[suffixCleanId(schema[i], '_ids')];
3301
- var thisConversion = void 0;
3302
- if (idList && idList.length > 0) {
3671
+ var idList_1 = $scope[suffixCleanId(schemaI, "_ids")];
3672
+ if (idList_1 && idList_1.length > 0) {
3303
3673
  updateObject(fieldname, anObject, function (value) {
3304
- return convertToForeignKeys(schema[i], value, $scope[suffixCleanId(schema[i], 'Options')], idList);
3674
+ return convertToForeignKeys(schemaI, value, $scope[suffixCleanId(schemaI, "Options")], idList_1);
3305
3675
  });
3306
3676
  }
3307
- else if (thisConversion = getConversionObject($scope, fieldname, schemaName)) {
3308
- var lookup = getData(anObject, fieldname, null);
3309
- var newVal;
3310
- if (schema[i].array) {
3311
- newVal = [];
3312
- if (lookup) {
3313
- for (var n = 0; n < lookup.length; n++) {
3314
- newVal[n] = convertLookup(lookup[n], thisConversion);
3677
+ else {
3678
+ var thisConversion = getConversionObject($scope, fieldname, schemaName);
3679
+ if (thisConversion) {
3680
+ var lookup = getData(anObject, fieldname, null);
3681
+ var newVal = void 0;
3682
+ if (schemaI.array) {
3683
+ newVal = [];
3684
+ if (lookup) {
3685
+ for (var n = 0; n < lookup.length; n++) {
3686
+ newVal[n] = convertLookup(lookup[n], thisConversion);
3687
+ }
3315
3688
  }
3316
3689
  }
3690
+ else {
3691
+ newVal = convertLookup(lookup, thisConversion);
3692
+ }
3693
+ setData(anObject, fieldname, null, newVal);
3317
3694
  }
3318
- else {
3319
- newVal = convertLookup(lookup, thisConversion);
3320
- }
3321
- setData(anObject, fieldname, null, newVal);
3322
3695
  }
3323
3696
  }
3697
+ };
3698
+ for (var i = 0; i < schema.length; i++) {
3699
+ _loop_1();
3324
3700
  }
3325
3701
  return anObject;
3326
3702
  },
@@ -3330,7 +3706,7 @@ var fng;
3330
3706
  $scope.handleHttpError = handleError($scope);
3331
3707
  $scope.cancel = function () {
3332
3708
  angular.copy(ctrlState.master, $scope.record);
3333
- $scope.$broadcast('fngCancel', $scope);
3709
+ $scope.$broadcast("fngCancel", $scope);
3334
3710
  // Let call backs etc resolve in case they dirty form, then clean it
3335
3711
  $timeout($scope.setPristine);
3336
3712
  };
@@ -3339,15 +3715,21 @@ var fng;
3339
3715
  // scope.$emit('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
3340
3716
  // or
3341
3717
  // scope.$broadcast('showErrorMessage', {title: 'Your error Title', body: 'The body of the error message'});
3342
- $scope.$on('showErrorMessage', function (event, args) {
3343
- $scope.showError(args.body, args.title);
3718
+ $scope.$on("showErrorMessage", function (event, args) {
3719
+ if (!event.defaultPrevented) {
3720
+ event.defaultPrevented = true;
3721
+ $scope.showError(args.body, args.title);
3722
+ }
3344
3723
  });
3345
3724
  $scope.showError = function (error, alertTitle) {
3346
- $scope.alertTitle = alertTitle ? alertTitle : 'Error!';
3347
- if (typeof error === 'string') {
3725
+ $scope.alertTitle = alertTitle ? alertTitle : "Error!";
3726
+ if (typeof error === "string") {
3348
3727
  $scope.errorMessage = error;
3349
3728
  }
3350
- else if (error.message && typeof error.message === 'string') {
3729
+ else if (!error) {
3730
+ $scope.errorMessage = "An error occurred - that's all we got. Sorry.";
3731
+ }
3732
+ else if (error.message && typeof error.message === "string") {
3351
3733
  $scope.errorMessage = error.message;
3352
3734
  }
3353
3735
  else if (error.data && error.data.message) {
@@ -3361,16 +3743,35 @@ var fng;
3361
3743
  $scope.errorMessage = error;
3362
3744
  }
3363
3745
  }
3746
+ $scope.errorHideTimer = window.setTimeout(function () {
3747
+ $scope.dismissError();
3748
+ $scope.$digest();
3749
+ }, 3500 + (1000 * ($scope.alertTitle + $scope.errorMessage).length / 50));
3750
+ $scope.errorVisible = true;
3751
+ window.setTimeout(function () {
3752
+ $scope.$digest();
3753
+ });
3754
+ };
3755
+ $scope.clearTimeout = function () {
3756
+ if ($scope.errorHideTimer) {
3757
+ clearTimeout($scope.errorHideTimer);
3758
+ delete $scope.errorHideTimer;
3759
+ }
3364
3760
  };
3365
3761
  $scope.dismissError = function () {
3762
+ $scope.clearTimeout;
3763
+ $scope.errorVisible = false;
3366
3764
  delete $scope.errorMessage;
3367
3765
  delete $scope.alertTitle;
3368
3766
  };
3767
+ $scope.stickError = function () {
3768
+ clearTimeout($scope.errorHideTimer);
3769
+ };
3369
3770
  $scope.prepareForSave = function (cb) {
3370
3771
  //Convert the lookup values into ids
3371
3772
  var dataToSave = recordHandlerInstance.convertToMongoModel($scope.formSchema, angular.copy($scope.record), 0, $scope);
3372
3773
  if ($scope.id) {
3373
- if (typeof $scope.dataEventFunctions.onBeforeUpdate === 'function') {
3774
+ if (typeof $scope.dataEventFunctions.onBeforeUpdate === "function") {
3374
3775
  $scope.dataEventFunctions.onBeforeUpdate(dataToSave, ctrlState.master, function (err) {
3375
3776
  if (err) {
3376
3777
  cb(err);
@@ -3385,7 +3786,7 @@ var fng;
3385
3786
  }
3386
3787
  }
3387
3788
  else {
3388
- if (typeof $scope.dataEventFunctions.onBeforeCreate === 'function') {
3789
+ if (typeof $scope.dataEventFunctions.onBeforeCreate === "function") {
3389
3790
  $scope.dataEventFunctions.onBeforeCreate(dataToSave, function (err) {
3390
3791
  if (err) {
3391
3792
  cb(err);
@@ -3404,22 +3805,24 @@ var fng;
3404
3805
  options = options || {};
3405
3806
  $scope.prepareForSave(function (err, dataToSave) {
3406
3807
  if (err) {
3407
- if (err !== '_update_handled_') {
3408
- $scope.showError(err);
3808
+ if (err !== "_update_handled_") {
3809
+ $timeout(function () {
3810
+ $scope.showError(err);
3811
+ });
3409
3812
  }
3410
3813
  }
3411
3814
  else if ($scope.id) {
3412
3815
  recordHandlerInstance.updateDocument(dataToSave, options, $scope, ctrlState);
3413
3816
  }
3414
3817
  else {
3415
- recordHandlerInstance.createNew(dataToSave, options, $scope);
3818
+ recordHandlerInstance.createNew(dataToSave, options, $scope, ctrlState);
3416
3819
  }
3417
3820
  });
3418
3821
  };
3419
3822
  $scope.newClick = function () {
3420
- routingService.redirectTo()('new', $scope, $location);
3823
+ routingService.redirectTo()("new", $scope, $location);
3421
3824
  };
3422
- $scope.$on('$locationChangeStart', function (event, next) {
3825
+ $scope.$on("$locationChangeStart", function (event, next) {
3423
3826
  // let changed = !$scope.isCancelDisabled();
3424
3827
  // let curPath = window.location.href.split('/');
3425
3828
  // let nextPath = next.split('/');
@@ -3438,21 +3841,12 @@ var fng;
3438
3841
  if (!ctrlState.allowLocationChange && !$scope.isCancelDisabled()) {
3439
3842
  event.preventDefault();
3440
3843
  var modalInstance = $uibModal.open({
3441
- template: '<div class="modal-header">' +
3442
- ' <h3>Record modified</h3>' +
3443
- '</div>' +
3444
- '<div class="modal-body">' +
3445
- ' <p>Would you like to save your changes?</p>' +
3446
- '</div>' +
3447
- '<div class="modal-footer">' +
3448
- ' <button class="btn btn-primary dlg-yes" ng-click="yes()">Yes</button>' +
3449
- ' <button class="btn btn-warning dlg-no" ng-click="no()">No</button>' +
3450
- ' <button class="btn dlg-cancel" ng-click="cancel()">Cancel</button>' +
3451
- '</div>',
3452
- controller: 'SaveChangesModalCtrl',
3453
- backdrop: 'static'
3844
+ 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>",
3845
+ controller: "SaveChangesModalCtrl",
3846
+ backdrop: "static"
3454
3847
  });
3455
- modalInstance.result.then(function (result) {
3848
+ modalInstance.result
3849
+ .then(function (result) {
3456
3850
  if (result) {
3457
3851
  $scope.save({ redirect: next, allowChange: true }); // save changes
3458
3852
  }
@@ -3460,7 +3854,8 @@ var fng;
3460
3854
  ctrlState.allowLocationChange = true;
3461
3855
  $window.location = next;
3462
3856
  }
3463
- });
3857
+ })
3858
+ .catch(_handleCancel);
3464
3859
  }
3465
3860
  });
3466
3861
  $scope.deleteClick = function () {
@@ -3471,85 +3866,168 @@ var fng;
3471
3866
  }
3472
3867
  else {
3473
3868
  var modalInstance = $uibModal.open({
3474
- template: '<div class="modal-header">' +
3475
- ' <h3>Delete Item</h3>' +
3476
- '</div>' +
3477
- '<div class="modal-body">' +
3478
- ' <p>Are you sure you want to delete this record?</p>' +
3479
- '</div>' +
3480
- '<div class="modal-footer">' +
3481
- ' <button class="btn btn-primary dlg-no" ng-click="cancel()">No</button>' +
3482
- ' <button class="btn btn-warning dlg-yes" ng-click="yes()">Yes</button>' +
3483
- '</div>',
3484
- controller: 'SaveChangesModalCtrl',
3485
- backdrop: 'static'
3869
+ 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>",
3870
+ controller: "SaveChangesModalCtrl",
3871
+ backdrop: "static"
3486
3872
  });
3487
3873
  confirmDelete = modalInstance.result;
3488
3874
  }
3489
3875
  confirmDelete.then(function (result) {
3876
+ function doTheDeletion() {
3877
+ recordHandlerInstance.deleteRecord($scope.id, $scope, ctrlState);
3878
+ }
3490
3879
  if (result) {
3491
- if (typeof $scope.dataEventFunctions.onBeforeDelete === 'function') {
3880
+ if (typeof $scope.dataEventFunctions.onBeforeDelete === "function") {
3492
3881
  $scope.dataEventFunctions.onBeforeDelete(ctrlState.master, function (err) {
3493
3882
  if (err) {
3494
- if (err !== '_delete_handled_') {
3883
+ if (err !== "_delete_handled_") {
3495
3884
  $scope.showError(err);
3496
3885
  }
3497
3886
  }
3498
3887
  else {
3499
- recordHandlerInstance.deleteRecord($scope.modelName, $scope.id, $scope, ctrlState);
3888
+ doTheDeletion();
3500
3889
  }
3501
3890
  });
3502
3891
  }
3503
3892
  else {
3504
- recordHandlerInstance.deleteRecord($scope.modelName, $scope.id, $scope, ctrlState);
3893
+ doTheDeletion();
3505
3894
  }
3506
3895
  }
3507
- });
3896
+ })
3897
+ .catch(_handleCancel);
3508
3898
  }
3509
3899
  };
3510
3900
  $scope.isCancelDisabled = function () {
3511
- if (typeof $scope.disableFunctions.isCancelDisabled === 'function') {
3901
+ if ($scope[$scope.topLevelFormName] && $scope[$scope.topLevelFormName].$pristine) {
3902
+ return true;
3903
+ }
3904
+ else if (typeof $scope.disableFunctions.isCancelDisabled === "function") {
3512
3905
  return $scope.disableFunctions.isCancelDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3513
3906
  }
3514
3907
  else {
3515
- return $scope[$scope.topLevelFormName] && $scope[$scope.topLevelFormName].$pristine;
3908
+ return false;
3516
3909
  }
3517
3910
  };
3518
3911
  $scope.isSaveDisabled = function () {
3519
- if (typeof $scope.disableFunctions.isSaveDisabled === 'function') {
3520
- return $scope.disableFunctions.isSaveDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3912
+ $scope.whyDisabled = undefined;
3913
+ var pristine = false;
3914
+ function generateWhyDisabledMessage(form, subFormName) {
3915
+ form.$$controls.forEach(function (c) {
3916
+ if (c.$invalid) {
3917
+ if (c.$$controls) {
3918
+ // nested form
3919
+ generateWhyDisabledMessage(c, c.$name);
3920
+ }
3921
+ else {
3922
+ $scope.whyDisabled += "<br /><strong>";
3923
+ if (subFormName) {
3924
+ $scope.whyDisabled += subFormName + ' ';
3925
+ }
3926
+ if (cssFrameworkService.framework() === "bs2" &&
3927
+ c.$$element &&
3928
+ c.$$element.parent() &&
3929
+ c.$$element.parent().parent() &&
3930
+ c.$$element.parent().parent().find("label") &&
3931
+ c.$$element.parent().parent().find("label").text()) {
3932
+ $scope.whyDisabled += c.$$element.parent().parent().find("label").text();
3933
+ }
3934
+ else if (cssFrameworkService.framework() === "bs3" &&
3935
+ c.$$element &&
3936
+ c.$$element.parent() &&
3937
+ c.$$element.parent().parent() &&
3938
+ c.$$element.parent().parent().parent() &&
3939
+ c.$$element.parent().parent().parent().find("label") &&
3940
+ c.$$element.parent().parent().parent().find("label").text()) {
3941
+ $scope.whyDisabled += c.$$element.parent().parent().parent().find("label").text();
3942
+ }
3943
+ else {
3944
+ $scope.whyDisabled += c.$name;
3945
+ }
3946
+ $scope.whyDisabled += "</strong>: ";
3947
+ if (c.$error) {
3948
+ for (var type in c.$error) {
3949
+ if (c.$error.hasOwnProperty(type)) {
3950
+ switch (type) {
3951
+ case "required":
3952
+ $scope.whyDisabled += "Field missing required value. ";
3953
+ break;
3954
+ case "pattern":
3955
+ $scope.whyDisabled += "Field does not match required pattern. ";
3956
+ break;
3957
+ default:
3958
+ $scope.whyDisabled += type + ". ";
3959
+ }
3960
+ }
3961
+ }
3962
+ }
3963
+ }
3964
+ }
3965
+ });
3966
+ }
3967
+ if ($scope[$scope.topLevelFormName]) {
3968
+ if ($scope[$scope.topLevelFormName].$invalid) {
3969
+ $scope.whyDisabled = 'The form data is invalid:';
3970
+ generateWhyDisabledMessage($scope[$scope.topLevelFormName]);
3971
+ }
3972
+ else if ($scope[$scope.topLevelFormName].$pristine) {
3973
+ // Don't have disabled message - should be obvious from Cancel being disabled,
3974
+ // and the message comes up when the Save button is clicked.
3975
+ pristine = true;
3976
+ }
3977
+ }
3978
+ else {
3979
+ $scope.whyDisabled = "Top level form name invalid";
3980
+ }
3981
+ if (pristine || !!$scope.whyDisabled) {
3982
+ return true;
3983
+ }
3984
+ else if (typeof $scope.disableFunctions.isSaveDisabled !== "function") {
3985
+ return false;
3521
3986
  }
3522
3987
  else {
3523
- return ($scope[$scope.topLevelFormName] && ($scope[$scope.topLevelFormName].$invalid || $scope[$scope.topLevelFormName].$pristine));
3988
+ var retVal = $scope.disableFunctions.isSaveDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3989
+ if (typeof retVal === "string") {
3990
+ $scope.whyDisabled = retVal;
3991
+ }
3992
+ else {
3993
+ $scope.whyDisabled = "An application level user-specified function is inhibiting saving the record";
3994
+ }
3995
+ return !!retVal;
3524
3996
  }
3525
3997
  };
3526
3998
  $scope.isDeleteDisabled = function () {
3527
- if (typeof $scope.disableFunctions.isDeleteDisabled === 'function') {
3999
+ if (!$scope.id) {
4000
+ return true;
4001
+ }
4002
+ else if (typeof $scope.disableFunctions.isDeleteDisabled === "function") {
3528
4003
  return $scope.disableFunctions.isDeleteDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3529
4004
  }
3530
4005
  else {
3531
- return (!$scope.id);
4006
+ return false;
3532
4007
  }
3533
4008
  };
3534
4009
  $scope.isNewDisabled = function () {
3535
- if (typeof $scope.disableFunctions.isNewDisabled === 'function') {
4010
+ if (typeof $scope.disableFunctions.isNewDisabled === "function") {
3536
4011
  return $scope.disableFunctions.isNewDisabled($scope.record, ctrlState.master, $scope[$scope.topLevelFormName]);
3537
4012
  }
3538
4013
  else {
3539
4014
  return false;
3540
4015
  }
3541
4016
  };
3542
- $scope.disabledText = function (localStyling) {
3543
- var text = '';
3544
- if ($scope.isSaveDisabled) {
3545
- text = 'This button is only enabled when the form is complete and valid. Make sure all required inputs are filled in. ' + localStyling;
3546
- }
3547
- return text;
4017
+ $scope.setDefaults = function (formSchema, base) {
4018
+ if (base === void 0) { base = ''; }
4019
+ var retVal = {};
4020
+ formSchema.forEach(function (s) {
4021
+ if (s.defaultValue !== undefined) {
4022
+ retVal[s.name.replace(base, '')] = s.defaultValue;
4023
+ }
4024
+ });
4025
+ return retVal;
3548
4026
  };
3549
4027
  $scope.getVal = function (expression, index) {
3550
- if (expression.indexOf('$index') === -1 || typeof index !== 'undefined') {
4028
+ if (expression.indexOf("$index") === -1 || typeof index !== "undefined") {
3551
4029
  expression = expression.replace(/\$index/g, index);
3552
- return $scope.$eval('record.' + expression);
4030
+ return $scope.$eval("record." + expression);
3553
4031
  }
3554
4032
  //else {
3555
4033
  // Used to show error here, but angular seems to call before record is populated sometimes
@@ -3563,6 +4041,23 @@ var fng;
3563
4041
  }
3564
4042
  }
3565
4043
  };
4044
+ $scope.setUpCustomLookupOptions = function (schemaElement, ids, options, baseScope) {
4045
+ for (var _i = 0, _a = [$scope, baseScope]; _i < _a.length; _i++) {
4046
+ var scope = _a[_i];
4047
+ if (scope) {
4048
+ // need to be accessible on our scope for generation of the select options, and - for nested schemas -
4049
+ // on baseScope for the conversion back to ids done by prepareForSave
4050
+ scope[schemaElement.ids] = ids;
4051
+ scope[schemaElement.options] = options;
4052
+ }
4053
+ }
4054
+ var data = getData($scope.record, schemaElement.name);
4055
+ if (!data) {
4056
+ return;
4057
+ }
4058
+ data = convertForeignKeys(schemaElement, data, options, ids);
4059
+ setData($scope.record, schemaElement.name, undefined, data);
4060
+ };
3566
4061
  },
3567
4062
  fillFormFromBackendCustomSchema: fillFormFromBackendCustomSchema,
3568
4063
  fillFormWithBackendSchema: function fillFormWithBackendSchema($scope, formGeneratorInstance, recordHandlerInstance, ctrlState) {
@@ -3595,13 +4090,45 @@ var fng;
3595
4090
  })(services = fng.services || (fng.services = {}));
3596
4091
  })(fng || (fng = {}));
3597
4092
  /// <reference path="../../../../node_modules/@types/angular/index.d.ts" />
4093
+ var ExpirationCache = /** @class */ (function () {
4094
+ function ExpirationCache(timeout) {
4095
+ if (timeout === void 0) { timeout = 60 * 1000; }
4096
+ this.store = new Map();
4097
+ this.timeout = timeout;
4098
+ }
4099
+ ExpirationCache.prototype.get = function (key) {
4100
+ // this.store.has(key) ? console.log(`cache hit`) : console.log(`cache miss`);
4101
+ return this.store.get(key);
4102
+ };
4103
+ ExpirationCache.prototype.put = function (key, val) {
4104
+ var _this = this;
4105
+ this.store.set(key, val);
4106
+ // remove it once it's expired
4107
+ setTimeout(function () {
4108
+ // console.log(`removing expired key ${key}`);
4109
+ _this.remove(key);
4110
+ }, this.timeout);
4111
+ };
4112
+ ExpirationCache.prototype.remove = function (key) {
4113
+ this.store.delete(key);
4114
+ };
4115
+ ExpirationCache.prototype.removeAll = function () {
4116
+ this.store = new Map();
4117
+ };
4118
+ ExpirationCache.prototype.delete = function () {
4119
+ //no op here because this is standalone, not a part of $cacheFactory
4120
+ };
4121
+ return ExpirationCache;
4122
+ }());
3598
4123
  var fng;
3599
4124
  (function (fng) {
3600
4125
  var services;
3601
4126
  (function (services) {
3602
4127
  /*@ngInject*/
3603
- SubmissionsService.$inject = ["$http", "$cacheFactory"];
3604
- function SubmissionsService($http, $cacheFactory) {
4128
+ SubmissionsService.$inject = ["$http"];
4129
+ function SubmissionsService($http) {
4130
+ var useCacheForGetAll = true;
4131
+ var expCache = new ExpirationCache();
3605
4132
  /*
3606
4133
  generate a query string for a filtered and paginated query for submissions.
3607
4134
  options consists of the following:
@@ -3659,11 +4186,7 @@ var fng;
3659
4186
  // };
3660
4187
  // },
3661
4188
  getListAttributes: function (ref, id) {
3662
- if (typeof ref === 'string') {
3663
- ref = { type: 'lookup', collection: ref };
3664
- console.warn("Support for string type \"ref\" property is deprecated - use ref:" + JSON.stringify(ref));
3665
- }
3666
- return $http.get('/api/' + ref.collection + '/' + id + '/list');
4189
+ return $http.get('/api/' + ref + '/' + id + '/list', { cache: expCache });
3667
4190
  },
3668
4191
  readRecord: function (modelName, id) {
3669
4192
  // TODO Figure out tab history updates (check for other tab-history-todos)
@@ -3679,7 +4202,7 @@ var fng;
3679
4202
  },
3680
4203
  getAll: function (modelName, _options) {
3681
4204
  var options = angular.extend({
3682
- cache: true
4205
+ cache: useCacheForGetAll ? expCache : false
3683
4206
  }, _options);
3684
4207
  return $http.get('/api/' + modelName, options);
3685
4208
  },
@@ -3690,12 +4213,21 @@ var fng;
3690
4213
  return $http.delete('/api/' + model + '/' + id);
3691
4214
  },
3692
4215
  updateRecord: function (modelName, id, dataToSave) {
3693
- $cacheFactory.get('$http').remove('/api/' + modelName);
4216
+ expCache.remove('/api/' + modelName);
3694
4217
  return $http.post('/api/' + modelName + '/' + id, dataToSave);
3695
4218
  },
3696
4219
  createRecord: function (modelName, dataToSave) {
3697
- $cacheFactory.get('$http').remove('/api/' + modelName);
4220
+ expCache.remove('/api/' + modelName);
3698
4221
  return $http.post('/api/' + modelName, dataToSave);
4222
+ },
4223
+ useCache: function (val) {
4224
+ useCacheForGetAll = val;
4225
+ },
4226
+ getCache: function () {
4227
+ return !!expCache;
4228
+ },
4229
+ clearCache: function () {
4230
+ expCache.removeAll();
3699
4231
  }
3700
4232
  };
3701
4233
  }
@@ -3721,6 +4253,7 @@ var fng;
3721
4253
  fngInvalidRequired: 'fng-invalid-required',
3722
4254
  allowLocationChange: true // Set when the data arrives..
3723
4255
  };
4256
+ $scope.errorVisible = false;
3724
4257
  angular.extend($scope, routingService.parsePathFunc()($location.$$path));
3725
4258
  // Load context menu. For /person/client/:id/edit we need
3726
4259
  // to load PersonCtrl and PersonClientCtrl
@@ -3735,17 +4268,33 @@ var fng;
3735
4268
  $rootScope.$broadcast('fngFormLoadStart', $scope);
3736
4269
  formGenerator.decorateScope($scope, formGenerator, recordHandler, $scope.sharedData);
3737
4270
  recordHandler.decorateScope($scope, $uibModal, recordHandler, ctrlState);
3738
- recordHandler.fillFormWithBackendSchema($scope, formGenerator, recordHandler, ctrlState);
3739
- // Tell the 'model controllers' that they can start fiddling with basescope
3740
- for (var i = 0; i < $scope.sharedData.modelControllers.length; i++) {
3741
- if ($scope.sharedData.modelControllers[i].onBaseCtrlReady) {
3742
- $scope.sharedData.modelControllers[i].onBaseCtrlReady($scope);
4271
+ function processTheForm() {
4272
+ recordHandler.fillFormWithBackendSchema($scope, formGenerator, recordHandler, ctrlState);
4273
+ // Tell the 'model controllers' that they can start fiddling with baseScope
4274
+ for (var i = 0; i < $scope.sharedData.modelControllers.length; i++) {
4275
+ if ($scope.sharedData.modelControllers[i].onBaseCtrlReady) {
4276
+ $scope.sharedData.modelControllers[i].onBaseCtrlReady($scope);
4277
+ }
3743
4278
  }
4279
+ $scope.$on('$destroy', function () {
4280
+ $scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
4281
+ $rootScope.$broadcast('fngControllersUnloaded');
4282
+ });
4283
+ }
4284
+ //Check that we are ready
4285
+ if (typeof fng.formsAngular.beforeProcess === "function") {
4286
+ fng.formsAngular.beforeProcess($scope, function (err) {
4287
+ if (err) {
4288
+ $scope.showError(err.message, 'Error preparing to process form');
4289
+ }
4290
+ else {
4291
+ processTheForm();
4292
+ }
4293
+ });
4294
+ }
4295
+ else {
4296
+ processTheForm();
3744
4297
  }
3745
- $scope.$on('$destroy', function () {
3746
- $scope.sharedData.modelControllers.forEach(function (value) { return value.$destroy(); });
3747
- $rootScope.$broadcast('fngControllersUnloaded');
3748
- });
3749
4298
  }
3750
4299
  controllers.BaseCtrl = BaseCtrl;
3751
4300
  })(controllers = fng.controllers || (fng.controllers = {}));
@@ -3801,13 +4350,17 @@ var fng;
3801
4350
  var controllers;
3802
4351
  (function (controllers) {
3803
4352
  /*@ngInject*/
3804
- NavCtrl.$inject = ["$scope", "$location", "$filter", "routingService", "cssFrameworkService"];
3805
- function NavCtrl($scope, $location, $filter, routingService, cssFrameworkService) {
4353
+ NavCtrl.$inject = ["$rootScope", "$scope", "$location", "$filter", "routingService", "cssFrameworkService"];
4354
+ function NavCtrl($rootScope, $scope, $location, $filter, routingService, cssFrameworkService) {
3806
4355
  function clearContextMenu() {
3807
4356
  $scope.items = [];
3808
4357
  $scope.contextMenu = undefined;
3809
4358
  }
4359
+ $rootScope.navScope = $scope; // Lets plugins access menus
3810
4360
  clearContextMenu();
4361
+ $scope.toggleCollapsed = function () {
4362
+ $scope.collapsed = !$scope.collapsed;
4363
+ };
3811
4364
  /* 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 */
3812
4365
  $scope.isCollapsed = true;
3813
4366
  $scope.showShortcuts = false;
@@ -3835,8 +4388,8 @@ var fng;
3835
4388
  }
3836
4389
  }
3837
4390
  function filter(event) {
3838
- var tagName = (event.target || event.srcElement).tagName;
3839
- return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA');
4391
+ var tagName = (event.target).tagName;
4392
+ return !(tagName == 'INPUT' || tagName == 'SELECT' || tagName == 'TEXTAREA' || tagName == "DIV" && event.target.classList.contains('ck-editor__editable'));
3840
4393
  }
3841
4394
  //console.log(event.keyCode, event.ctrlKey, event.shiftKey, event.altKey, event.metaKey);
3842
4395
  if (event.keyCode === 191 && (filter(event) || (event.ctrlKey && !event.altKey && !event.metaKey))) {
@@ -3865,7 +4418,7 @@ var fng;
3865
4418
  else if (event.keyCode === 45 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
3866
4419
  deferredBtnClick('newButton'); // Ctrl+Shift+Ins creates New record
3867
4420
  }
3868
- else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && event.altKey && !event.metaKey) {
4421
+ else if (event.keyCode === 88 && event.ctrlKey && event.shiftKey && !event.altKey && !event.metaKey) {
3869
4422
  deferredBtnClick('deleteButton'); // Ctrl+Shift+X deletes record
3870
4423
  }
3871
4424
  };
@@ -3881,6 +4434,11 @@ var fng;
3881
4434
  };
3882
4435
  $scope.$on('fngControllersLoaded', function (evt, sharedData, modelName) {
3883
4436
  $scope.contextMenu = sharedData.dropDownDisplay || sharedData.modelNameDisplay || $filter('titleCase')(modelName, false);
4437
+ if (sharedData.dropDownDisplayPromise) {
4438
+ sharedData.dropDownDisplayPromise.then(function (value) {
4439
+ $scope.contextMenu = value;
4440
+ });
4441
+ }
3884
4442
  });
3885
4443
  $scope.$on('fngControllersUnloaded', function (evt) {
3886
4444
  clearContextMenu();
@@ -3896,28 +4454,59 @@ var fng;
3896
4454
  }
3897
4455
  else {
3898
4456
  // Performance optimization: http://jsperf.com/apply-vs-call-vs-invoke
3899
- var args = item.args || [], fn = item.fn;
3900
- switch (args.length) {
3901
- case 0:
3902
- fn();
3903
- break;
3904
- case 1:
3905
- fn(args[0]);
3906
- break;
3907
- case 2:
3908
- fn(args[0], args[1]);
3909
- break;
3910
- case 3:
3911
- fn(args[0], args[1], args[2]);
3912
- break;
3913
- case 4:
3914
- fn(args[0], args[1], args[2], args[3]);
3915
- break;
4457
+ var args = item.args || [];
4458
+ var fn = item.fn;
4459
+ if (typeof fn === "function") {
4460
+ switch (args.length) {
4461
+ case 0:
4462
+ fn();
4463
+ break;
4464
+ case 1:
4465
+ fn(args[0]);
4466
+ break;
4467
+ case 2:
4468
+ fn(args[0], args[1]);
4469
+ break;
4470
+ case 3:
4471
+ fn(args[0], args[1], args[2]);
4472
+ break;
4473
+ case 4:
4474
+ fn(args[0], args[1], args[2], args[3]);
4475
+ break;
4476
+ }
4477
+ }
4478
+ else if (fn) {
4479
+ throw new Error("Incorrect menu setup");
3916
4480
  }
3917
4481
  }
3918
4482
  };
3919
4483
  $scope.isHidden = function (index) {
3920
- return $scope.items[index].isHidden ? $scope.items[index].isHidden() : false;
4484
+ function explicitlyHidden(item) {
4485
+ return item.isHidden ? item.isHidden() : false;
4486
+ }
4487
+ var dividerHide = false;
4488
+ // Hide a divider if it appears under another
4489
+ if ($scope.items[index].divider) {
4490
+ if (index === 0) {
4491
+ dividerHide = true;
4492
+ }
4493
+ else {
4494
+ var foundVisible = false;
4495
+ var check = index - 1;
4496
+ while (check >= 0 && !dividerHide && !foundVisible) {
4497
+ if ($scope.items[check].divider) {
4498
+ dividerHide = true;
4499
+ }
4500
+ else if (!explicitlyHidden($scope.items[check])) {
4501
+ foundVisible = true;
4502
+ }
4503
+ else {
4504
+ --check;
4505
+ }
4506
+ }
4507
+ }
4508
+ }
4509
+ return dividerHide || explicitlyHidden($scope.items[index]);
3921
4510
  };
3922
4511
  $scope.isDisabled = function (index) {
3923
4512
  return $scope.items[index].isDisabled ? $scope.items[index].isDisabled() : false;
@@ -3976,6 +4565,7 @@ var fng;
3976
4565
  ])
3977
4566
  .controller('BaseCtrl', fng.controllers.BaseCtrl)
3978
4567
  .controller('SaveChangesModalCtrl', fng.controllers.SaveChangesModalCtrl)
4568
+ .controller('LinkCtrl', fng.controllers.LinkCtrl)
3979
4569
  .controller('ModelCtrl', fng.controllers.ModelCtrl)
3980
4570
  .controller('NavCtrl', fng.controllers.NavCtrl)
3981
4571
  .directive('modelControllerDropdown', fng.directives.modelControllerDropdown)
@@ -3987,6 +4577,7 @@ var fng;
3987
4577
  .directive('fngNakedDate', fng.directives.fngNakedDate)
3988
4578
  .filter('camelCase', fng.filters.camelCase)
3989
4579
  .filter('titleCase', fng.filters.titleCase)
4580
+ .filter('extractTimestampFromMongoID', fng.filters.extractTimestampFromMongoID)
3990
4581
  .service('addAllService', fng.services.addAllService)
3991
4582
  .provider('cssFrameworkService', fng.services.cssFrameworkService)
3992
4583
  .provider('routingService', fng.services.routingService)
@@ -4002,11 +4593,15 @@ var fng;
4002
4593
  // expose the library
4003
4594
  var formsAngular = fng.formsAngular;
4004
4595
 
4005
- 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>{{ reportSchema.title }}</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 <error-display></error-display>\n <div class="row-fluid">\n <div class="gridStyle" ui-grid="gridOptions" ui-grid-selection></div>\n </div>\n </div>\n</div>\n');
4006
- $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 <h4>{{modelNameDisplay}} :\n <span ng-repeat="field in listSchema">{{getListData(record, field.name)}} </span>\n </h4>\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 <error-display></error-display>\n <form-input name="baseForm" schema="baseSchema()" formstyle="compact"></form-input>\n </div>\n <!--{{ formSchema | json }}-->\n</div>\n');
4007
- $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 <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 <error-display></error-display>\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');
4596
+ angular.module('formsAngular').run(['$templateCache', function($templateCache) {$templateCache.put('base-analysis.html','<div ng-controller="AnalysisCtrl">\n <error-display></error-display>\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');
4597
+ $templateCache.put('base-edit.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\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');
4598
+ $templateCache.put('base-list-view.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\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');
4599
+ $templateCache.put('base-list.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\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');
4600
+ $templateCache.put('base-view.html','<div ng-controller="BaseCtrl">\n <error-display></error-display>\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');
4601
+ $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');
4602
+ $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');
4008
4603
  $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');
4009
- $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');
4010
- $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');
4011
- $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 <span ng-class="resultClass($index)" ng-click="selectResult($index)">{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');
4012
- $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 <span ng-class="resultClass($index)" ng-click="selectResult($index)" title={{result.additional}}>{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');}]);
4604
+ $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');
4605
+ $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');
4606
+ $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 <span ng-class="resultClass($index)" ng-click="selectResult($index)">{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');
4607
+ $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 <span ng-class="resultClass($index)" ng-click="selectResult($index)" title={{result.additional}}>{{result.resourceText}} {{result.text}}</span>\n </div>\n <div ng-show="moreCount > 0">(plus more - continue typing to narrow down search...)\n </div>\n </div>\n</div>\n');}]);